Files
FastapiAdmin/backend/app/modules/generator/gencode/jinja2_template_util.py
T

748 lines
29 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import re
from datetime import datetime
from typing import Any
from jinja2 import Environment, FileSystemLoader, Template
from app.common.constant import GenConstant
from app.config.path_conf import TEMPLATE_DIR
from app.config.setting import settings
from app.utils.common_util import CamelCaseUtil, SnakeCaseUtil, compute_menu_route_first_segment
from app.utils.string_util import StringUtil
from .gen_util import GenUtils
from .schema import (
GenTableColumnOutSchema,
GenTableOutSchema,
)
class Jinja2TemplateUtil:
"""模板处理工具类
"""
@classmethod
def normalize_db_column_type_for_mapping(cls, column_type: str | None) -> str:
"""与 ``GenUtils.get_db_type`` 一致地去掉 COLLATE / UNSIGNED,便于与 ``DB_TO_SQLALCHEMY`` 键匹配。
参数:
- column_type (str | None): 原始列类型字符串。
返回:
- str: 规范化后的类型片段;空输入返回空字符串。
"""
ct = (column_type or "").strip()
if not ct:
return ""
collate_pattern = re.compile(r"\s+COLLATE\s+", re.IGNORECASE)
if collate_pattern.search(ct):
ct = collate_pattern.split(ct)[0].strip()
unsigned_pattern = re.compile(r"\s+UNSIGNED", re.IGNORECASE)
if unsigned_pattern.search(ct):
ct = unsigned_pattern.sub("", ct).strip()
return ct
# 项目路径
FRONTEND_PROJECT_PATH = "frontend/web"
BACKEND_PROJECT_PATH = "backend"
# 环境对象
_env = None
@classmethod
def get_env(cls):
"""获取模板环境对象。
参数:
- 无
返回:
- Environment: Jinja2 环境对象。
"""
try:
if cls._env is None:
cls._env = Environment(
loader=FileSystemLoader(TEMPLATE_DIR),
autoescape=False, # 自动转义HTML
trim_blocks=True, # 删除多余的空行
lstrip_blocks=True, # 删除行首空格
keep_trailing_newline=True, # 保留行尾换行符
enable_async=True, # 开启异步支持
)
cls._env.filters.update(
{
"camel_to_snake": SnakeCaseUtil.camel_to_snake,
"snake_to_camel": CamelCaseUtil.snake_to_camel,
"get_sqlalchemy_type": cls.get_sqlalchemy_type,
"python_to_ts_type": cls.python_type_to_ts_type,
},
)
return cls._env
except Exception as e:
raise RuntimeError(f"初始化Jinja2模板引擎失败: {e}")
@classmethod
def get_template(cls, template_path: str) -> Template:
"""获取模板。
参数:
- template_path (str): 模板路径。
返回:
- Template: Jinja2 模板对象。
异常:
- TemplateNotFound: 模板未找到时抛出。
"""
return cls.get_env().get_template(template_path)
@classmethod
def business_name_to_slug(cls, business_name: str | None) -> str:
"""业务路径可含斜杠(如 ``demo/subdir``)用于目录与路由前缀;
Python 函数/方法名仅使用最后一段并规范为合法 snake_case 片段。
参数:
- business_name (str | None): 业务路径或名称。
返回:
- str: 用于 Python 标识的 slug,默认 entity。
"""
s = (business_name or "").strip().strip("/")
if not s:
return "entity"
if "/" in s:
s = s.split("/")[-1]
s = re.sub(r"[^a-zA-Z0-9_]", "_", s)
if not s:
return "entity"
if s[0].isdigit():
s = "_" + s
return s
@classmethod
def business_name_to_path(cls, business_name: str | None) -> str:
"""把 business_name 规范为可用于目录/路由的多段路径(保留 `/`)。
约定:`business_name` 允许 `a/b/c` 表示多级菜单目录。
- 目录/路由:使用完整多段
- 文件名/route_name:使用最后一段 slug(见 `business_name_to_slug`
参数:
- business_name (str | None): 业务路径或名称。
返回:
- str: 多段路径字符串(小写 slug),默认 entity。
"""
s = (business_name or "").strip().strip("/")
if not s:
return "entity"
# 每段都做一次轻度规范(与 schema 的 slug 规则一致:a-z0-9_
segs = []
for raw in [p for p in s.split("/") if p]:
seg = re.sub(r"[^a-zA-Z0-9_]", "_", raw).lower()
seg = re.sub(r"_+", "_", seg).strip("_") or "entity"
if seg[0].isdigit():
seg = "_" + seg
segs.append(seg)
return "/".join(segs) if segs else "entity"
@classmethod
def prepare_context(cls, gen_table: GenTableOutSchema) -> dict[str, Any]:
"""准备模板变量。
参数:
- gen_table (GenTableOutSchema): 生成表的配置信息。
返回:
- Dict[str, Any]: 模板上下文字典。
"""
# 处理options为None的情况
# if not gen_table.options:
# raise ValueError('请先完善生成配置信息')
class_name = gen_table.class_name or ""
package_name = (gen_table.package_name or "").strip()
module_name = (gen_table.module_name or "").strip()
business_name = (gen_table.business_name or "").strip()
function_name = gen_table.function_name or ""
# 生成规则(对齐 module_example/demo):
# - 分系统根:package_name = module_xxx
# - 目录固定为:module_xxx / module_name(不再额外使用业务名作为目录层级)
# - 权限前缀固定为:module_xxx:module_name(操作在模板里再拼 :query/:create...
business_path = cls.business_name_to_path(business_name)
business_name_slug = cls.business_name_to_slug(business_name)
permission_prefix = ":".join([s for s in [package_name, module_name] if s])
api_route_prefix = cls.get_api_route_prefix(package_name)
_cols = gen_table.columns or []
table_column_names = frozenset(c.column_name for c in _cols if getattr(c, "column_name", None))
has_dict_column = any(getattr(c, "dict_type", None) for c in _cols)
has_image_column = any(getattr(c, "html_type", None) == "imageUpload" for c in _cols)
has_json_column = any(getattr(c, "python_type", None) == "dict" for c in _cols)
sub_class_name = ""
sub_model_class_name = ""
sub_rel_list_name = ""
parent_rel_name = ""
if gen_table.sub and gen_table.sub_table:
st = gen_table.sub_table
scn = (st.class_name or GenUtils.convert_class_name(gen_table.sub_table_name or "")).strip()
sub_class_name = scn
sub_model_class_name = f"{scn}Model"
sub_rel_list_name = f"{SnakeCaseUtil.camel_to_snake(scn)}_list"
parent_rel_name = SnakeCaseUtil.camel_to_snake(gen_table.class_name or "")
context = {
"table_name": gen_table.table_name or "",
"table_comment": gen_table.table_comment or "",
"function_name": function_name if StringUtil.is_not_empty(function_name) else "【请填写功能名称】",
"class_name": class_name,
"module_name": module_name,
"business_name": business_name,
"business_path": business_path,
"business_file": business_name_slug,
"business_name_slug": business_name_slug,
"base_package": cls.get_package_prefix(package_name),
"package_name": package_name,
"menu_route_first_segment": cls.get_menu_route_first_segment(gen_table),
"datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"pk_column": gen_table.pk_column,
"model_import_list": cls.get_model_import_list(gen_table),
"schema_import_list": cls.get_schema_import_list(gen_table),
"permission_prefix": permission_prefix,
"api_route_prefix": api_route_prefix,
"columns": gen_table.columns or [],
"table_column_names": table_column_names,
"table": gen_table,
"dicts": cls.get_dicts(gen_table),
"db_type": settings.DATABASE_TYPE,
"column_not_add_show": GenConstant.COLUMNNAME_NOT_ADD_SHOW,
"column_not_edit_show": GenConstant.COLUMNNAME_NOT_EDIT_SHOW,
"parent_menu_id": int(gen_table.parent_menu_id) if gen_table.parent_menu_id else None,
"is_sub_entity": False,
"sub_class_name": sub_class_name,
"sub_model_class_name": sub_model_class_name,
"sub_module_name": (gen_table.sub_table.module_name if gen_table.sub and gen_table.sub_table else ""),
"sub_rel_list_name": sub_rel_list_name,
"parent_rel_name": parent_rel_name,
"parent_list_rel_name": "",
"parent_table_name": "",
"parent_model_class_name": "",
# 数据表实际主键列名(用于生成前端行键等;ModelMixin 仍默认带 id 字段)
"pk_column_name": (gen_table.pk_column.column_name if gen_table.pk_column else None) or "id",
"parent_pk_column_name": (gen_table.pk_column.column_name if gen_table.pk_column else None) or "id",
"sub_table_fk_name": "",
"has_dict_column": has_dict_column,
"has_image_column": has_image_column,
"has_json_column": has_json_column,
}
return context
@classmethod
def get_menu_route_first_segment(cls, gen_table: GenTableOutSchema) -> str:
"""前端页面路由首段(与写入菜单 ``route_path`` 第一段一致):始终为 ``module_xxx``。"""
pid = int(gen_table.parent_menu_id) if gen_table.parent_menu_id is not None else None
return compute_menu_route_first_segment(
pid,
gen_table.package_name or "",
gen_table.module_name,
)
@classmethod
def prepare_sub_render_context(cls, parent: GenTableOutSchema, sub: GenTableOutSchema) -> dict[str, Any]:
"""子表业务代码渲染上下文(与主表同模块、独立业务目录)。
参数:
- parent (GenTableOutSchema): 主表配置。
- sub (GenTableOutSchema): 子表配置。
返回:
- dict[str, Any]: 子表模板上下文字典。
"""
ctx = cls.prepare_context(sub)
scn = (sub.class_name or GenUtils.convert_class_name(sub.table_name or "")).strip()
ctx["is_sub_entity"] = True
ctx["parent_class_name"] = parent.class_name or ""
ctx["parent_model_class_name"] = f"{parent.class_name}Model"
ctx["parent_table_name"] = parent.table_name or ""
ctx["parent_pk_column_name"] = (parent.pk_column.column_name if parent.pk_column else None) or "id"
ctx["parent_rel_name"] = SnakeCaseUtil.camel_to_snake(parent.class_name or "parent")
ctx["parent_list_rel_name"] = f"{SnakeCaseUtil.camel_to_snake(scn)}_list"
ctx["sub_table_fk_name"] = (parent.sub_table_fk_name or "").strip()
ctx["model_import_list"] = cls.get_model_import_list(sub, is_sub_entity=True)
ctx["schema_import_list"] = cls.get_schema_import_list(sub)
return ctx
@classmethod
def get_template_list(cls):
"""获取主表模板列表。
参数:
- 无
返回:
- List[str]: 模板路径列表。
"""
templates = [
"python/controller.py.jinja2",
"python/service.py.jinja2",
"python/crud.py.jinja2",
"python/schema.py.jinja2",
"python/model.py.jinja2",
"python/__init__.py.jinja2",
"ts/api.ts.jinja2",
"vue/index.vue.jinja2",
]
return templates
@classmethod
def get_sub_table_template_list(cls):
"""获取子表模板列表(仅 model / schema / __init__,不含 controller/service/crud/vue/api)。
参数:
- 无
返回:
- List[str]: 子表模板路径列表。
"""
return [
"python/model.py.jinja2",
"python/schema.py.jinja2",
"python/__init__.py.jinja2",
]
@classmethod
def get_file_name(cls, template: str, gen_table: GenTableOutSchema):
"""根据模板生成文件名。
参数:
- template (str): 模板路径字符串。
- gen_table (GenTableOutSchema): 生成表的配置信息。
返回:
- str: 模板生成的文件名。
异常:
- ValueError: 当无法生成有效文件名时抛出。
"""
package_name = (gen_table.package_name or "").strip()
module_name = (gen_table.module_name or "").strip()
if not package_name:
raise ValueError(f"无法为模板 {template} 生成文件名:包名未设置")
if not module_name:
raise ValueError(f"无法为模板 {template} 生成文件名:模块名未设置")
# 目录固定为:module_xxx/{module_name}
backend_base = f"{cls.BACKEND_PROJECT_PATH}/app/plugin/{package_name}"
frontend_view_base = f"{cls.FRONTEND_PROJECT_PATH}/src/views/{package_name}"
frontend_api_base = f"{cls.FRONTEND_PROJECT_PATH}/src/api/{package_name}"
backend_dir = f"{backend_base}/{module_name}"
view_dir = f"{frontend_view_base}/{module_name}"
api_path = f"{frontend_api_base}/{module_name}.ts"
template_mapping = {
"controller.py.jinja2": f"{backend_dir}/controller.py",
"service.py.jinja2": f"{backend_dir}/service.py",
"crud.py.jinja2": f"{backend_dir}/crud.py",
"schema.py.jinja2": f"{backend_dir}/schema.py",
"model.py.jinja2": f"{backend_dir}/model.py",
"__init__.py.jinja2": f"{backend_dir}/__init__.py",
"api.ts.jinja2": api_path,
"index.vue.jinja2": f"{view_dir}/index.vue",
}
# 查找匹配的模板路径
for key, path in template_mapping.items():
if key in template:
return path
# 遍历完所有映射都没找到匹配项,才抛出异常
raise ValueError(f"未找到模板 '{template}' 的路径映射")
@classmethod
def get_package_prefix(cls, package_name: str) -> str:
"""获取包前缀。
参数:
- package_name (str): 包名。
返回:
- str: 包前缀。
"""
# 修复:当包名中不存在'.'时,直接返回原包名
return package_name[: package_name.rfind(".")] if "." in package_name else package_name
@classmethod
def get_schema_import_list(cls, gen_table: GenTableOutSchema):
"""获取 schema 模板所需的 Python 导入语句集合。
参数:
- gen_table (GenTableOutSchema): 生成表配置(含主子表列)。
返回:
- set[str]: 导入语句字符串集合。
"""
columns = gen_table.columns or []
import_list = set()
has_datetime_import = False
has_date_import = False
has_time_import = False
for column in columns:
# 处理datetime类型的导入
if column.python_type and column.python_type in GenConstant.TYPE_DATE:
if column.python_type == "datetime":
has_datetime_import = True
elif column.python_type == "date":
has_date_import = True
elif column.python_type == "time":
has_time_import = True
elif column.python_type == GenConstant.TYPE_DECIMAL:
import_list.add("from decimal import Decimal")
if gen_table.sub and gen_table.sub_table and gen_table.sub_table.columns:
sub_columns = gen_table.sub_table.columns or []
for sub_column in sub_columns:
# 处理datetime类型的导入
if sub_column.python_type and sub_column.python_type in GenConstant.TYPE_DATE:
if sub_column.python_type == "datetime":
has_datetime_import = True
elif sub_column.python_type == "date":
has_date_import = True
elif sub_column.python_type == "time":
has_time_import = True
elif sub_column.python_type == GenConstant.TYPE_DECIMAL:
import_list.add("from decimal import Decimal")
# 添加datetime导入
if has_datetime_import:
import_list.add("from datetime import datetime")
if has_date_import:
import_list.add("from datetime import date")
if has_time_import:
import_list.add("from datetime import time")
return import_list
@classmethod
def get_model_import_list(cls, gen_table: GenTableOutSchema, *, is_sub_entity: bool = False) -> list[str]:
"""获取 model 模板所需的 Python 导入语句列表(含合并后的 sqlalchemy 导入)。
参数:
- gen_table (GenTableOutSchema): 生成表配置。
- is_sub_entity (bool): 是否为子表独立生成(含外键与 relationship)。
返回:
- list[str]: 导入语句列表。
"""
columns = gen_table.columns or []
import_list = set()
has_datetime_import = False
has_date_import = False
has_time_import = False
# 基类 ModelMixin/UserMixin 已定义的列,无需导入 SQLAlchemy 类型
_BASE_MODEL_COLUMNS = {
"id",
"uuid",
"created_time",
"updated_time",
"created_id",
"updated_id",
"is_deleted",
"deleted_time",
"deleted_id",
}
for column in columns:
if column.column_name in _BASE_MODEL_COLUMNS:
continue
if column.column_type:
data_type = cls.get_db_type(column.column_type)
if data_type in GenConstant.COLUMNTYPE_GEOMETRY:
import_list.add("from geoalchemy2 import Geometry")
import_list.add(f"from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, data_type)}")
# 处理datetime类型的导入
if column.python_type and column.python_type in GenConstant.TYPE_DATE:
if column.python_type == "datetime":
has_datetime_import = True
elif column.python_type == "date":
has_date_import = True
elif column.python_type == "time":
has_time_import = True
# 处理Decimal类型的导入
elif column.python_type == GenConstant.TYPE_DECIMAL:
import_list.add("from decimal import Decimal")
# 子实体独立生成时才需要外键导入(主表仅声明 relationship,无需 ForeignKey;子表列的 SQLAlchemy
# 类型由其自身模型渲染时通过 columns 循环导入)
if is_sub_entity:
import_list.add("from sqlalchemy import ForeignKey")
# 添加datetime导入
if has_datetime_import:
import_list.add("from datetime import datetime")
if has_date_import:
import_list.add("from datetime import date")
if has_time_import:
import_list.add("from datetime import time")
merged = cls.merge_same_imports(list(import_list), "from sqlalchemy import")
if gen_table.sub or is_sub_entity:
merged.append("from sqlalchemy.orm import relationship")
return merged
@classmethod
def get_db_type(cls, column_type: str) -> str:
"""获取数据库字段类型。
参数:
- column_type (str): 字段类型字符串。
返回:
- str: 数据库类型(去除长度等修饰)。
"""
# 移除 COLLATE 子句(处理带引号和不带引号的情况,不区分大小写)
collate_pattern = re.compile(r"\s+COLLATE\s+", re.IGNORECASE)
if collate_pattern.search(column_type):
column_type = collate_pattern.split(column_type)[0].strip()
# 移除 UNSIGNED 标记(不区分大小写)
unsigned_pattern = re.compile(r"\s+UNSIGNED", re.IGNORECASE)
if unsigned_pattern.search(column_type):
column_type = unsigned_pattern.sub("", column_type).strip()
# 处理PostgreSQL数组类型(如 integer[], text[]
if "[]" in column_type:
return "array"
# 提取基本类型
if "(" in column_type:
return column_type.split("(")[0]
return column_type
@classmethod
def merge_same_imports(cls, imports: list[str], import_start: str) -> list[str]:
"""合并相同的导入语句。
参数:
- imports (list[str]): 导入语句列表。
- import_start (str): 导入语句的起始字符串。
返回:
- list[str]: 合并后的导入语句列表。
"""
merged_imports = []
imports_ = []
for import_stmt in imports:
if import_stmt.startswith(import_start):
imported_items = import_stmt.split("import")[1].strip()
imports_.extend(imported_items.split(", "))
else:
merged_imports.append(import_stmt)
if imports_:
# 去重并过滤空字符串,然后用逗号连接
unique_imports = [item for item in imports_ if item]
if len(unique_imports) > 0:
merged_datetime_import = f"{import_start} {', '.join(unique_imports)}"
merged_imports.append(merged_datetime_import)
return merged_imports
@classmethod
def get_dicts(cls, gen_table: GenTableOutSchema):
"""获取字典列表。
参数:
- gen_table (GenTableOutSchema): 生成表的配置信息。
返回:
- str: 以逗号分隔的字典类型字符串。
"""
columns = gen_table.columns or []
dicts = set()
cls.add_dicts(dicts, columns)
# 处理sub_table为None的情况
if gen_table.sub_table is not None:
# 处理sub_table.columns为None的情况
sub_columns = gen_table.sub_table.columns or []
cls.add_dicts(dicts, sub_columns)
return ", ".join(dicts)
@classmethod
def add_dicts(cls, dicts: set[str], columns: list[GenTableColumnOutSchema]) -> None:
"""添加字典类型到集合。
参数:
- dicts (set[str]): 字典类型集合。
- columns (list[GenTableColumnOutSchema]): 字段列表。
返回:
- set[str]: 更新后的字典类型集合。
"""
for column in columns:
super_column = column.super_column if column.super_column is not None else "0"
dict_type = column.dict_type or ""
html_type = column.html_type or ""
if (
super_column != "1"
and StringUtil.is_not_empty(dict_type)
and StringUtil.equals_any_ignore_case(
html_type,
[
GenConstant.HTML_SELECT,
GenConstant.HTML_RADIO,
GenConstant.HTML_CHECKBOX,
],
)
):
dicts.add(f"'{dict_type}'")
@classmethod
def get_permission_prefix(cls, module_name: str | None, business_name: str | None) -> str:
"""获取权限前缀。
参数:
- module_name (str | None): 模块名。
- business_name (str | None): 业务名。
返回:
- str: 权限前缀字符串。
"""
mn = (module_name or "").strip()
bn = (business_name or "").strip().replace("/", ":")
if not bn:
return mn
return f"{mn}:{bn}"
@classmethod
def python_type_to_ts_type(cls, python_type: str | None) -> str:
"""将列上的 Python 类型(`get_db_type` + `DB_TO_PYTHON` 映射结果)转为前端 TS 类型片段。
与 JSON 序列化习惯一致:Decimal、日期时间多为字符串;dict/list 用宽松类型。
参数:
- python_type (str | None): Python 类型名。
返回:
- str: 前端 TypeScript 类型片段。
"""
if not python_type or not str(python_type).strip():
return "string"
p = str(python_type).strip()
mapping: dict[str, str] = {
"int": "number",
"float": "number",
"bool": "boolean",
"Decimal": "string",
"date": "string",
"time": "string",
"datetime": "string",
"timedelta": "string",
"dict": "Record<string, unknown>",
"list": "unknown[]",
"bytes": "string",
"str": "string",
}
return mapping.get(p, "string")
@classmethod
def get_api_route_prefix(cls, module_name: str | None) -> str:
"""获取前端 API 路径首段,与 `discover` 中插件路由前缀一致(`module_xxx` → `xxx`)。
参数:
- module_name (str | None): 模块名,如 ``module_example``。
返回:
- str: 路由前缀,如 ``example``。
"""
if not module_name:
return ""
if module_name.startswith("module_"):
return module_name[7:]
return module_name
@classmethod
def get_sqlalchemy_type(cls, column: Any) -> str:
"""获取 SQLAlchemy 类型。
参数:
- column (Any): 列对象或列类型字符串。
返回:
- str: SQLAlchemy 类型字符串。
"""
# 获取column_type和column_length
column_type = column
column_length = None
# 检查是否是对象
if hasattr(column, "column_type"):
column_type = column.column_type or ""
column_length = column.column_length or None
column_type = cls.normalize_db_column_type_for_mapping(column_type)
# MySQL:仅 tinyint(1) 映射为 Boolean;其余 tinyint 走 SmallInteger(见 GenConstant.DB_TO_SQLALCHEMY
ct_norm = (column_type or "").strip()
if settings.DATABASE_TYPE != "postgres" and ct_norm:
ct_lower = ct_norm.lower()
if ct_lower.startswith("tinyint(1)"):
return "Boolean"
# 首先尝试匹配完整类型(包括括号)
sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, column_type)
# 特殊处理PostgreSQL类型
if settings.DATABASE_TYPE == "postgres":
if column_type.upper() == "BOOLEAN":
return "Boolean"
if column_type.upper() == "REAL" or column_type.upper() == "DOUBLE PRECISION":
return "Float"
if column_type.upper() == "TIMESTAMP":
return "DateTime"
if column_type.upper() == "JSONB":
return "JSONB"
if column_type.upper() == "UUID":
return "Uuid"
if column_type.upper() == "BYTEA":
return "LargeBinary"
# get_mapping_value_by_key_ignore_case 未命中时返回 "",须与 None 同样视为未匹配
if not sqlalchemy_type and "(" in column_type:
# 如果没有匹配到,再尝试剥离括号
column_type_list = column_type.split("(")
col_type = column_type_list[0]
# 将 'character' 映射为 'char' 以匹配常量定义
if col_type.lower() == "character":
col_type = "char"
sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, col_type)
# 如果是字符串类型且包含括号参数,保持原参数
if sqlalchemy_type in ["String", "CHAR"] or sqlalchemy_type in ["Numeric", "DECIMAL"]:
sqlalchemy_type += "(" + column_type_list[1]
elif not sqlalchemy_type:
# 处理没有括号的类型
col_type = column_type
# 将 'character' 映射为 'char' 以匹配常量定义
if col_type.lower() == "character":
col_type = "char"
sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, col_type)
# 如果是字符串类型且没有指定长度,使用column_length或默认255
if sqlalchemy_type in ["String", "CHAR"]:
length = column_length if column_length and column_length.isdigit() else "255"
sqlalchemy_type += f"({length})"
# 对于已经匹配到的类型,如果是字符串类型且column有长度信息,添加长度
elif sqlalchemy_type in ["String", "CHAR"] and "(" not in sqlalchemy_type:
# 检查column_length是否有效
length = column_length if column_length and column_length.isdigit() else "255"
sqlalchemy_type += f"({length})"
# 如果没有找到匹配的类型,使用String(column_length)或String(255)作为默认类型
if not sqlalchemy_type:
length = column_length if column_length and column_length.isdigit() else "255"
sqlalchemy_type = f"String({length})"
return sqlalchemy_type