使用python 一键生成,PGSQL的数据字典

直接上代码

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PostgreSQL 数据字典生成器 (Python 3.11+)
生成完全离线的 HTML 文件,可直接双击在浏览器中打开。
"""

import psycopg2
import datetime
import os
import sys
from typing import List, Dict, Any

# 数据库配置(请根据实际情况修改)
DB_CONFIG = {
    "host": "127.0.0.1",
    "port": 5432,
    "database": "postgres",
    "user": "postgres",
    "password": "postgres"
}

def get_tables_and_comments(conn) -> List[Dict[str, str]]:
    """获取 public 模式下所有用户表及其注释"""
    with conn.cursor() as cur:
        cur.execute("""
            SELECT
                t.table_name,
                obj_description(c.oid, 'pg_class') AS table_comment
            FROM information_schema.tables t
            JOIN pg_class c ON c.relname = t.table_name
            WHERE t.table_schema = 'public'
              AND t.table_type = 'BASE TABLE'
            ORDER BY t.table_name;
        """)
        return [
            {"TABLE_NAME": row[0], "TABLE_COMMENT": row[1] or ""}
            for row in cur.fetchall()
        ]

def get_columns_info(conn, table_name: str, table_schema: str = 'public') -> List[Dict[str, Any]]:
    """获取指定表的所有列详细信息"""
    with conn.cursor() as cur:
        # 获取列注释(使用 pg_* 系统表查询)
        cur.execute("""
            SELECT a.attname, COALESCE(col_description(a.attrelid, a.attnum), '')
            FROM pg_catalog.pg_attribute a
            JOIN pg_catalog.pg_class c ON c.oid = a.attrelid
            JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
            WHERE n.nspname = %s AND c.relname = %s AND a.attnum > 0 AND NOT a.attisdropped
            ORDER BY a.attnum;
        """, (table_schema, table_name))
        comments = {row[0]: row[1] for row in cur.fetchall()}

        # 获取列基本信息
        cur.execute("""
            SELECT
                c.column_name,
                c.data_type,
                c.column_default,
                c.is_nullable,
                CASE
                    WHEN c.column_default LIKE 'nextval(%%' THEN TRUE
                    ELSE FALSE
                END AS is_auto_increment
            FROM information_schema.columns c
            WHERE c.table_schema = %s AND c.table_name = %s
            ORDER BY c.ordinal_position;
        """, (table_schema, table_name))

        columns = []
        for row in cur.fetchall():
            if row is None:
                continue
            col_name = row[0] if len(row) > 0 else ""
            columns.append({
                "COLUMN_NAME": col_name,
                "COLUMN_TYPE": row[1] if len(row) > 1 else "",
                "COLUMN_DEFAULT": row[2] if len(row) > 2 else "",
                "IS_NULLABLE": row[3] if len(row) > 3 else "NO",
                "IS_AUTO_INCREMENT": row[4] if len(row) > 4 else False,
                "COLUMN_COMMENT": comments.get(col_name, "")
            })
        return columns

def generate_offline_html(tables: List[Dict], db_name: str) -> str:
    """生成完整的离线 HTML 内容"""
    # 表清单 - 使用百分比宽度,自动适应页面
    table_list_html = '<table border="1" cellspacing="0" cellpadding="5" style="width:100%;table-layout:fixed;">\n'
    table_list_html += '<colgroup><col style="width:8%"><col style="width:25%"><col style="width:67%"></colgroup>\n'
    table_list_html += '<thead><tr><th>序号</th><th>表名</th><th>功能说明</th></tr></thead>\n<tbody>\n'
    for idx, tbl in enumerate(tables, 1):
        table_list_html += f'  <tr><td>{idx}</td><td>{tbl["TABLE_NAME"]}</td><td>{tbl["TABLE_COMMENT"]}</td></tr>\n'
    table_list_html += '</tbody>\n</table>\n'

    # 字段详情
    detail_html = ""
    for tbl in tables:
        detail_html += '<table border="1" cellspacing="0" cellpadding="5" style="width:100%;table-layout:fixed;">\n'
        detail_html += '<colgroup><col style="width:15%"><col style="width:12%"><col style="width:15%"><col style="width:8%"><col style="width:8%"><col style="width:42%"></colgroup>\n'
        comment_part = f' - {tbl["TABLE_COMMENT"]}' if tbl["TABLE_COMMENT"] else ""
        detail_html += f'  <caption><b>表名:{tbl["TABLE_NAME"]}{comment_part}</b></caption>\n'
        detail_html += '  <thead>\n    <tr><th>字段名</th><th>数据类型</th><th>默认值</th><th>允许非空</th><th>自动递增</th><th>备注</th></tr>\n  </thead>\n  <tbody>\n'
        for col in tbl.get("COLUMNS", []):
            nullable_display = "是" if col["IS_NULLABLE"] == "YES" else "否"
            auto_inc_display = "是" if col["IS_AUTO_INCREMENT"] else "-"
            detail_html += (
                f'    <tr>'
                f'<td>{col["COLUMN_NAME"]}</td>'
                f'<td>{col["COLUMN_TYPE"]}</td>'
                f'<td>{col["COLUMN_DEFAULT"]}</td>'
                f'<td>{nullable_display}</td>'
                f'<td>{auto_inc_display}</td>'
                f'<td>{col["COLUMN_COMMENT"]}</td>'
                f'</tr>\n'
            )
        detail_html += '  </tbody>\n</table>\n<br/>\n'

    # 完整 HTML
    now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    return f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <title>{db_name} 数据字典</title>
    <style>
        body {{ font-family: "宋体", "SimSun", serif; font-size: 12pt; margin: 20px; background: white; color: black; }}
        h1 {{ text-align: center; color: black; margin-bottom: 15px; font-size: 16pt; }}
        .summary {{ text-align: center; color: #666666; margin-bottom: 20px; font-size: 11pt; }}
        table {{ border-collapse: collapse; margin-bottom: 20px; page-break-inside: avoid; word-wrap: break-word; }}
        caption {{ text-align: left; font-weight: bold; font-size: 12pt; color: black; padding: 6px 0; }}
        th, td {{ border: 1px solid #000000; padding: 5px 6px; text-align: left; vertical-align: top; font-size: 11pt; word-wrap: break-word; }}
        thead th {{ background-color: #E0E0E0; font-weight: bold; }}
        .footer {{ text-align: left; margin-top: 20px; color: #666666; font-size: 11pt; }}
    </style>
</head>
<body>
    <h1>{db_name} 数据字典</h1>
    <p class="summary">生成时间:{now}</p>
    {table_list_html}
    <br/>
    {detail_html}
    <p class="footer">总共:{len(tables)} 个数据表</p>
</body>
</html>'''

def main():
    try:
        print("🔍 正在连接 PostgreSQL 数据库...")
        with psycopg2.connect(**DB_CONFIG) as conn:
            tables = get_tables_and_comments(conn)
            print(f"✅ 成功获取 {len(tables)} 张表")

            total_cols = 0
            for tbl in tables:
                cols = get_columns_info(conn, tbl["TABLE_NAME"])
                tbl["COLUMNS"] = cols
                total_cols += len(cols)

            html_content = generate_offline_html(tables, DB_CONFIG["database"])

            # 生成带时间戳的文件名
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            output_file = f'{DB_CONFIG["database"]}_数据字典_{timestamp}.html'

            with open(output_file, "w", encoding="utf-8") as f:
                f.write(html_content)

            abs_path = os.path.abspath(output_file)
            print(f"🎉 数据字典已生成!")
            print(f"📁 文件路径: {abs_path}")
            print(f"📊 共 {len(tables)} 张表,{total_cols} 个字段")
            print("\n💡 提示:双击该 HTML 文件即可在浏览器中查看!")

    except psycopg2.Error as e:
        print(f"❌ 数据库错误: {e}")
        sys.exit(1)
    except Exception as e:
        print(f"💥 程序异常: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == "__main__":
    main()

效果如下图所示:

相关推荐
七老板的blog8 分钟前
从持久化任务到多 Agent 协作
python·学习·ai
XGeFei22 分钟前
python中子线程与主线程的关系
开发语言·python
Chase_______25 分钟前
【Java杂项】final 关键字详解:变量、方法、类限制与引用可变性
java·开发语言·python
我材不敲代码36 分钟前
Python venv 虚拟环境从入门到精通 + uv 高性能替代工具实战指南
开发语言·python·uv
l1t1 小时前
DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程18-20
开发语言·python
零梦ing1 小时前
Claude Code 升级后 DeepSeek API 报错 messages[x].role: unknown variant system 终极解决方案
python·claude code·deepseek api 代理
Eiceblue1 小时前
Python 操作 Excel:数据分组、分类汇总与取消分组全解
开发语言·python·excel
暴躁小师兄数据学院2 小时前
【AI大模型应用开发工程师特训笔记】第04讲(第9章):文件目录操作
人工智能·笔记·python
TechWayfarer2 小时前
IP精准定位服务在快递网点规划中的应用:如何用客户位置数据辅助选址
大数据·网络·python·tcp/ip·交通物流
CSND7402 小时前
零基础学Python合集---3:字符串的定义和常用方法
人工智能·python