Files
playwrite/config/schema.py
Misaka Server f02858d5dc feat: add MySQL database support with SQL translation
- Add MySQLConnection class with automatic SQL Server to MySQL translation
- Add connection factory to support both SQL Server and MySQL
- Update config schema to support MySQL configuration (host, port, db_type)
- Update default config to use MySQL (localhost:3306)
- Translate table names: [schema].[table] -> schema_table
- Translate placeholders: ? -> %s
- Translate MERGE statements to INSERT ... ON DUPLICATE KEY UPDATE

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 20:12:32 +08:00

223 lines
6.8 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.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
配置结构定义
使用 dataclass 定义所有配置项的结构和类型。
"""
from dataclasses import dataclass, field
from typing import Optional
from pathlib import Path
@dataclass
class ERPConfig:
"""ERP 系统配置"""
url: str
username: str
password: str
headless: bool = True
ignore_https_errors: bool = True
auto_close_browser: bool = True
def validate(self) -> list[str]:
"""验证配置,返回错误列表"""
errors = []
if not self.url:
errors.append("ERP URL 不能为空")
if not self.username:
errors.append("ERP 用户名不能为空")
if not self.password:
errors.append("ERP 密码不能为空")
return errors
@dataclass
class DatabaseConfig:
"""数据库配置"""
server: str
database: str
username: str
password: str
driver: str = "ODBC Driver 18 for SQL Server"
trust_server_certificate: str = "yes"
# MySQL 支持字段
db_type: str = "sqlserver" # "sqlserver" 或 "mysql"
host: Optional[str] = None # MySQL 主机地址
port: Optional[int] = None # MySQL 端口
def validate(self) -> list[str]:
"""验证配置,返回错误列表"""
errors = []
if self.db_type == "mysql":
# MySQL 配置验证
if not self.host:
errors.append("MySQL 主机地址不能为空")
if not self.port:
errors.append("MySQL 端口不能为空")
else:
# SQL Server 配置验证
if not self.server:
errors.append("数据库服务器地址不能为空")
# 通用配置验证
if not self.database:
errors.append("数据库名称不能为空")
if not self.username:
errors.append("数据库用户名不能为空")
if not self.password:
errors.append("数据库密码不能为空")
return errors
@dataclass
class PathConfig:
"""文件路径配置"""
data_dir: str
production_id_file: str
default_output: str = "离散备料计划维护_合并.xlsx"
validation_output: str = "物料状态校验结果.xlsx"
def validate(self) -> list[str]:
"""验证配置,返回错误列表"""
errors = []
if not self.data_dir:
errors.append("数据目录路径不能为空")
if not self.production_id_file:
errors.append("ProductionID 文件路径不能为空")
return errors
@dataclass
class ExtractionConfig:
"""数据提取配置"""
batch_size: int = 100
verbose: bool = True
auto_convert: bool = True
merge_batches: bool = True
enable_db_persistence: bool = False
def validate(self) -> list[str]:
"""验证配置,返回错误列表"""
errors = []
if self.batch_size <= 0:
errors.append("批次大小必须大于 0")
if self.batch_size > 1000:
errors.append("批次大小不应超过 1000")
return errors
@dataclass
class ValidationConfig:
"""物料校验配置"""
data_source: str = "database_full"
use_database: bool = True
batch_size: int = 2000
enable_crud_operations: bool = False
default_manager: str = ""
match_mode: str = "substring"
def validate(self) -> list[str]:
"""验证配置,返回错误列表"""
errors = []
valid_sources = [
"database_full",
"database_filtered",
"excel_existing",
"excel_full"
]
if self.data_source not in valid_sources:
errors.append(
f"无效的数据源: {self.data_source}"
f"有效选项: {', '.join(valid_sources)}"
)
if self.batch_size <= 0:
errors.append("批次大小必须大于 0")
if self.batch_size > 2000:
errors.append("批次大小不应超过 2000SQL Server 参数限制)")
valid_match_modes = ["substring", "exact"]
if self.match_mode not in valid_match_modes:
errors.append(
f"无效的匹配模式: {self.match_mode}"
f"有效选项: {', '.join(valid_match_modes)}"
)
return errors
@dataclass
class AppConfig:
"""应用总配置"""
erp: ERPConfig
database: DatabaseConfig
paths: PathConfig
extraction: ExtractionConfig
validation: ValidationConfig
def validate(self) -> list[str]:
"""验证所有配置,返回错误列表"""
errors = []
errors.extend(self.erp.validate())
errors.extend(self.database.validate())
errors.extend(self.paths.validate())
errors.extend(self.extraction.validate())
errors.extend(self.validation.validate())
return errors
def to_dict(self) -> dict:
"""转换为字典格式(用于保存到 JSON"""
return {
"erp": {
"url": self.erp.url,
"username": self.erp.username,
"password": self.erp.password,
"headless": self.erp.headless,
"ignore_https_errors": self.erp.ignore_https_errors,
"auto_close_browser": self.erp.auto_close_browser,
},
"database": {
"server": self.database.server,
"database": self.database.database,
"username": self.database.username,
"password": self.database.password,
"driver": self.database.driver,
"trust_server_certificate": self.database.trust_server_certificate,
"db_type": self.database.db_type,
"host": self.database.host,
"port": self.database.port,
},
"paths": {
"data_dir": self.paths.data_dir,
"production_id_file": self.paths.production_id_file,
"default_output": self.paths.default_output,
"validation_output": self.paths.validation_output,
},
"extraction": {
"batch_size": self.extraction.batch_size,
"verbose": self.extraction.verbose,
"auto_convert": self.extraction.auto_convert,
"merge_batches": self.extraction.merge_batches,
"enable_db_persistence": self.extraction.enable_db_persistence,
},
"validation": {
"data_source": self.validation.data_source,
"use_database": self.validation.use_database,
"batch_size": self.validation.batch_size,
"enable_crud_operations": self.validation.enable_crud_operations,
"default_manager": self.validation.default_manager,
"match_mode": self.validation.match_mode,
},
}