Files
playwrite/config/schema.py
Misaka aaa46ef282 feat: migrate configuration to .env environment variables
This commit implements a complete migration from JSON-based configuration
to .env environment variables, providing better security and flexibility.

Key Changes:
- Add python-dotenv dependency for environment variable support
- Create config/env_loader.py with type conversion utilities
- Add from_env() class methods to all config dataclasses
- Update ConfigLoader to prioritize environment variables
- Add save_to_env() method for .env file management
- Implement database connection factory pattern
- Add base DAO and connection classes for better abstraction
- Support both SQL Server and MySQL with unified interface
- Create migration script (scripts/migrate_to_env.py)
- Update GUI to read/write .env files
- Add comprehensive migration documentation

New Files:
- config/env_loader.py - Environment variable loader
- db/base_connection.py - Base database connection interface
- db/base_dao.py - Base DAO with common utilities
- db/connection_factory.py - Factory for creating connections
- db/mysql_connection.py - MySQL-specific connection
- db/sqlserver_connection.py - SQL Server-specific connection
- db/table_name_converter.py - SQL dialect converter
- scripts/migrate_to_env.py - Configuration migration tool
- docs/ENV_MIGRATION.md - Complete migration guide
- .env.example - Environment variable template

Testing:
- Verified MySQL connection (8.0.44)
- Tested all DAO operations
- Confirmed 150 tables accessible
- Validated configuration loading

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 22:39:14 +08:00

352 lines
12 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
from enum import Enum
class DatabaseType(str, Enum):
"""数据库类型枚举"""
SQLSERVER = "sqlserver"
MYSQL = "mysql"
@dataclass
class ERPConfig:
"""ERP 系统配置"""
url: str
username: str
password: str
headless: bool = True
ignore_https_errors: bool = True
auto_close_browser: bool = True
@classmethod
def from_env(cls) -> "ERPConfig":
"""从环境变量创建配置"""
from config.env_loader import get_env, get_env_bool
return cls(
url=get_env("ERP_URL", "https://68.11.34.30:8082/"),
username=get_env("ERP_USERNAME", "BLDpengqiangqiang"),
password=get_env("ERP_PASSWORD", ""),
headless=get_env_bool("ERP_HEADLESS", True),
ignore_https_errors=get_env_bool("ERP_IGNORE_HTTPS_ERRORS", True),
auto_close_browser=get_env_bool("ERP_AUTO_CLOSE_BROWSER", 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 SQLServerConfig:
"""SQL Server 特定配置"""
driver: str = "ODBC Driver 18 for SQL Server"
trust_server_certificate: str = "yes"
@classmethod
def from_env(cls) -> "SQLServerConfig":
"""从环境变量创建配置"""
from config.env_loader import get_env
return cls(
driver=get_env("DB_SQLSERVER_DRIVER", "ODBC Driver 18 for SQL Server"),
trust_server_certificate=get_env("DB_TRUST_SERVER_CERTIFICATE", "yes"),
)
@dataclass
class MySQLConfig:
"""MySQL 特定配置"""
host: str = ""
port: int = 3306
charset: str = "utf8mb4"
@classmethod
def from_env(cls) -> "MySQLConfig":
"""从环境变量创建配置"""
from config.env_loader import get_env, get_env_int
return cls(
host=get_env("DB_MYSQL_HOST", "192.168.31.83"),
port=get_env_int("DB_MYSQL_PORT", 3306),
charset=get_env("DB_MYSQL_CHARSET", "utf8mb4"),
)
@dataclass
class DatabaseConfig:
"""数据库配置"""
db_type: DatabaseType = DatabaseType.SQLSERVER
server: str = "" # SQL Server 服务器地址
database: str = ""
username: str = ""
password: str = ""
sqlserver: Optional[SQLServerConfig] = None
mysql: Optional[MySQLConfig] = None
@classmethod
def from_env(cls) -> "DatabaseConfig":
"""从环境变量创建配置"""
from config.env_loader import get_env, get_env_int
db_type_str = get_env("DB_TYPE", "sqlserver")
try:
db_type = DatabaseType(db_type_str)
except ValueError:
db_type = DatabaseType.SQLSERVER
return cls(
db_type=db_type,
server=get_env("DB_SERVER", "192.168.110.114"),
database=get_env("DB_NAME", "CompanyDB"),
username=get_env("DB_USERNAME", "peng"),
password=get_env("DB_PASSWORD", ""),
sqlserver=SQLServerConfig.from_env(),
mysql=MySQLConfig.from_env(),
)
def validate(self) -> list[str]:
"""验证配置,返回错误列表"""
errors = []
if self.db_type == DatabaseType.SQLSERVER:
if not self.server:
errors.append("SQL Server 服务器地址不能为空")
if not self.database:
errors.append("数据库名称不能为空")
if not self.username:
errors.append("数据库用户名不能为空")
if not self.password:
errors.append("数据库密码不能为空")
elif self.db_type == DatabaseType.MYSQL:
if self.mysql and not self.mysql.host:
errors.append("MySQL 主机地址不能为空")
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"
@classmethod
def from_env(cls) -> "PathConfig":
"""从环境变量创建配置"""
from config.env_loader import get_env
return cls(
data_dir=get_env("PATH_DATA_DIR", "D:/python/playwrite/data/"),
production_id_file=get_env("PATH_PRODUCTION_ID_FILE", "ProductionID.txt"),
default_output=get_env("PATH_DEFAULT_OUTPUT", "离散备料计划维护_合并.xlsx"),
validation_output=get_env("PATH_VALIDATION_OUTPUT", "物料状态校验结果.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
@classmethod
def from_env(cls) -> "ExtractionConfig":
"""从环境变量创建配置"""
from config.env_loader import get_env_int, get_env_bool
return cls(
batch_size=get_env_int("EXTRACTION_BATCH_SIZE", 100),
verbose=get_env_bool("EXTRACTION_VERBOSE", True),
auto_convert=get_env_bool("EXTRACTION_AUTO_CONVERT", True),
merge_batches=get_env_bool("EXTRACTION_MERGE_BATCHES", True),
enable_db_persistence=get_env_bool("EXTRACTION_ENABLE_DB_PERSISTENCE", 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"
@classmethod
def from_env(cls) -> "ValidationConfig":
"""从环境变量创建配置"""
from config.env_loader import get_env, get_env_int, get_env_bool
return cls(
data_source=get_env("VALIDATION_DATA_SOURCE", "database_full"),
use_database=get_env_bool("VALIDATION_USE_DATABASE", True),
batch_size=get_env_int("VALIDATION_BATCH_SIZE", 2000),
enable_crud_operations=get_env_bool("VALIDATION_ENABLE_CRUD", False),
default_manager=get_env("VALIDATION_DEFAULT_MANAGER", ""),
match_mode=get_env("VALIDATION_MATCH_MODE", "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
@classmethod
def from_env(cls) -> "AppConfig":
"""从环境变量创建配置"""
return cls(
erp=ERPConfig.from_env(),
database=DatabaseConfig.from_env(),
paths=PathConfig.from_env(),
extraction=ExtractionConfig.from_env(),
validation=ValidationConfig.from_env(),
)
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": {
"db_type": self.database.db_type.value,
"server": self.database.server,
"database": self.database.database,
"username": self.database.username,
"password": self.database.password,
"sqlserver": {
"driver": self.database.sqlserver.driver if self.database.sqlserver else "ODBC Driver 18 for SQL Server",
"trust_server_certificate": self.database.sqlserver.trust_server_certificate if self.database.sqlserver else "yes",
},
"mysql": {
"host": self.database.mysql.host if self.database.mysql else "",
"port": self.database.mysql.port if self.database.mysql else 3306,
"charset": self.database.mysql.charset if self.database.mysql else "utf8mb4",
},
},
"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,
},
}