vmysql-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ Metadata-Version: 2.4
2
+ Name: vmysql_cli
3
+ Version: 0.1.0
4
+ Summary: 自然语言操作MySQL——输入中文即可查询、插入、修改数据库
5
+ Author: vvv
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/你的用户名/vmysql-cli
8
+ Project-URL: Issues, https://github.com/你的用户名/vmysql-cli/issues
9
+ Keywords: mysql,nl2sql,cli,ai,deepseek
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Database
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: sqlalchemy>=2.0
22
+ Requires-Dist: pandas>=2.0
23
+ Requires-Dist: openai>=1.0
24
+ Requires-Dist: pymysql>=1.0
@@ -0,0 +1,6 @@
1
+ vmysql_cli.py,sha256=KSPqSmHFc9niTA_-Da9vSApuSKLqe5VxwIMG9GfpQZc,8268
2
+ vmysql_cli-0.1.0.dist-info/METADATA,sha256=rDI0KlbETJdTaUrlPPptygeoR0SKElPuN9sac0kZ_j4,971
3
+ vmysql_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
4
+ vmysql_cli-0.1.0.dist-info/entry_points.txt,sha256=X3xiEh4KENcYcCntuYaRYN-RsiX98OGy4GZvK_2L-jk,47
5
+ vmysql_cli-0.1.0.dist-info/top_level.txt,sha256=NQdlomBkpsyh2BtlpDVv84Fj2S0uNQNBtUoTR6M_B-Y,11
6
+ vmysql_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ vmysql_cli = vmysql_cli:main
@@ -0,0 +1 @@
1
+ vmysql_cli
vmysql_cli.py ADDED
@@ -0,0 +1,214 @@
1
+ import time
2
+ import json
3
+ from pathlib import Path
4
+ import sqlalchemy as a
5
+ import pandas as b
6
+ from openai import OpenAI
7
+
8
+ ds_client = None
9
+ DB_URL = None
10
+ CONFIG_FILE = Path.home() / ".vmysql_cli_config.json"
11
+
12
+ def _load_config():
13
+ """加载配置:环境变量 > 本地配置文件 > 交互式输入"""
14
+ global ds_client, DB_URL
15
+ config = {}
16
+
17
+ # 1. 尝试从配置文件加载
18
+ if CONFIG_FILE.exists():
19
+ try:
20
+ config = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
21
+ except Exception:
22
+ pass
23
+
24
+ # 2. 缺失的配置交互式询问
25
+ prompted = False
26
+ if "api_key" not in config:
27
+ config["api_key"] = input("请输入 DeepSeek API Key: ").strip()
28
+ prompted = True
29
+ if "base_url" not in config:
30
+ print("API 地址示例:")
31
+ print(" DeepSeek → https://api.deepseek.com/v1")
32
+ print(" Ollama → http://localhost:11434/v1")
33
+ config["base_url"] = input("请输入 API Base URL: ").strip()
34
+ prompted = True
35
+ # 3. 缺失的数据库配置交互式询问
36
+ if "db_url" not in config:
37
+ host = input("数据库 IP 地址 (默认 127.0.0.1): ").strip() or "127.0.0.1"
38
+ port = input("端口 (默认 3306): ").strip() or "3306"
39
+ user = input("用户名 (默认 root): ").strip() or "root"
40
+ pwd = input("密码: ").strip()
41
+ config["db_url"] = f"mysql+pymysql://{user}:{pwd}@{host}:{port}/db0?charset=utf8"
42
+ prompted = True
43
+
44
+ # 3. 保存配置文件(仅在首次交互输入时)
45
+ if prompted:
46
+ try:
47
+ CONFIG_FILE.write_text(json.dumps(config, ensure_ascii=False, indent=2), encoding="utf-8")
48
+ print(f"配置已保存到 {CONFIG_FILE}")
49
+ except Exception:
50
+ pass
51
+ pass
52
+
53
+ ds_client = OpenAI(api_key=config["api_key"], base_url=config["base_url"])
54
+ DB_URL = config["db_url"]
55
+
56
+ _engine = None
57
+ _schema_cache = None
58
+ _preview_cache = None
59
+ _sql_cache = {}
60
+
61
+ def _get_engine():
62
+ global _engine
63
+ if _engine is None:
64
+ _engine = a.create_engine(DB_URL)
65
+ return _engine
66
+
67
+ def get_database_schema():
68
+ global _schema_cache
69
+ if _schema_cache is not None:
70
+ return _schema_cache
71
+ engine = _get_engine()
72
+
73
+ # 2. 查询数据库所有表
74
+ tables_df = b.read_sql("SHOW TABLES", engine)
75
+ table_names = tables_df.iloc[:, 0].tolist() # 提取所有表名
76
+
77
+ # 3. 遍历每张表,查询字段结构,并拼接成文本
78
+ lines = ["【当前数据库完整结构】\n"]
79
+ for table in table_names:
80
+ lines.append(f"\n表名:{table}\n")
81
+ struct_df = b.read_sql(f"SHOW FULL COLUMNS FROM `{table}`", engine)
82
+ for _, row in struct_df.iterrows():
83
+ extras = []
84
+ if row['Null'] == 'NO':
85
+ extras.append('NOT NULL')
86
+ if row['Key'] == 'PRI':
87
+ extras.append('PRIMARY KEY')
88
+ if 'auto_increment' in str(row['Extra']).lower():
89
+ extras.append('AUTO_INCREMENT')
90
+ if row['Default'] is not None:
91
+ extras.append(f'DEFAULT={row["Default"]}')
92
+ extra_str = ' [' + ' '.join(extras) + ']' if extras else ''
93
+ lines.append(f"- {row['Field']} ({row['Type']}){extra_str}\n")
94
+ _schema_cache = ''.join(lines)
95
+ return _schema_cache
96
+ def get_data_preview():
97
+ """查每张表的前20条数据,让AI看到实际内容(有缓存)"""
98
+ global _preview_cache
99
+ if _preview_cache is not None:
100
+ return _preview_cache
101
+ try:
102
+ tables = list(b.read_sql("SHOW TABLES", _get_engine()).iloc[:, 0])
103
+ except Exception:
104
+ return ""
105
+ preview = "\n【当前各表数据预览(最多20行)】\n"
106
+ for t in tables:
107
+ try:
108
+ df = b.read_sql(f"SELECT * FROM `{t}` LIMIT 20", _get_engine())
109
+ preview += f"\n表 {t} ({len(df)} 行):\n{df.to_string(index=False)}\n"
110
+ except Exception:
111
+ pass
112
+ _preview_cache = preview
113
+ return preview
114
+
115
+ _prompt_prefix_cache = None
116
+
117
+ def _get_prompt_prefix():
118
+ """缓存 prompt 前缀,避免每次重建大片文本"""
119
+ global _prompt_prefix_cache
120
+ if _prompt_prefix_cache is not None:
121
+ return _prompt_prefix_cache
122
+ _prompt_prefix_cache = f'''
123
+ 我现在的数据库真实结构如下:
124
+ {get_database_schema()}
125
+ {get_data_preview()}
126
+ 你是MySQL数据库,数据库名为db0。上面列出的就是全部可用的表和字段,没有其他表。
127
+ 请结合数据预览理解用户意图(如"无意义的数据""异常数据"等需根据实际数据判断)。
128
+
129
+ 请严格遵守规则:
130
+ 1. 只输出长度为2的Python列表,无多余文字
131
+ 2. 第1项:SQL语句,必须使用:xxx命名占位符
132
+ 3. 第2项:Python字典,作为查询参数,不是字符串
133
+ 4. 允许SELECT/INSERT/UPDATE/DELETE/DROP/CREATE,DELETE和UPDATE必须带WHERE
134
+ 5. 操作已有表时只能使用上面列出的表名和字段名,CREATE TABLE可以定义新字段
135
+ 6. 禁止使用information_schema、sys、mysql等系统库
136
+ 7. INSERT时永远不要包含自增主键列(如id),让数据库自动生成
137
+
138
+ 示例1:["select * from student where id = :id", {{"id": 1}}]
139
+ 示例2:["select * from student", {{}}]
140
+ 问"有哪些表"示例3:["show tables", {{}}]
141
+ 插入示例:["insert into student(name,age) values(:n,:a)", {{"n":"张三","a":20}}]
142
+ 批量插入:["insert into student(name,age) values(:n1,:a1),(:n2,:a2)", {{"n1":"张三","a1":20,"n2":"李四","a2":22}}]
143
+ '''.strip()
144
+ return _prompt_prefix_cache
145
+
146
+ def get_nl_to_sql(user_input):
147
+ if user_input in _sql_cache:
148
+ return _sql_cache[user_input], 0.0, 0.0
149
+ t0 = time.perf_counter()
150
+ prompt = _get_prompt_prefix()
151
+ t1 = time.perf_counter()
152
+
153
+ res = ds_client.chat.completions.create(
154
+ model="deepseek-chat",
155
+ messages=[{"role":"system","content":prompt},{"role":"user","content":user_input}],
156
+ temperature=0
157
+ )
158
+ raw = res.choices[0].message.content.strip()
159
+ t2 = time.perf_counter()
160
+ result = eval(raw)
161
+ _sql_cache[user_input] = result
162
+ return result, t1 - t0, t2 - t1
163
+ def _fill_sql(sql, params):
164
+ """把 :xxx 占位符替换为实际参数值,按key长度降序避免 :n 错误替换 :n1"""
165
+ result = sql
166
+ for k in sorted(params.keys(), key=len, reverse=True):
167
+ v = params[k]
168
+ if v is None:
169
+ result = result.replace(f":{k}", "NULL")
170
+ elif isinstance(v, (int, float)):
171
+ result = result.replace(f":{k}", str(v))
172
+ else:
173
+ result = result.replace(f":{k}", f"'{v}'")
174
+ return result
175
+
176
+ def sql(c):
177
+ c = c.strip()
178
+ # 本地处理表结构查询,不走AI(仅精确匹配)
179
+ if c in ('表结构', '有哪些表', '有什么表', '表有哪些', '表', '库', '结构', 'show tables'):
180
+ print("[耗时] Schema: 0.00s | AI: 0.00s | 执行: 0.00s (本地处理)")
181
+ if c == '表':
182
+ tables = b.read_sql("SHOW TABLES", _get_engine()).iloc[:, 0].tolist()
183
+ return '\n'.join(tables)
184
+ schema = _schema_cache or get_database_schema()
185
+ return schema
186
+ d, t_schema, t_ai = get_nl_to_sql(c)
187
+ t1 = time.perf_counter()
188
+ sql_upper = d[0].strip().upper()
189
+ if sql_upper.startswith(('INSERT', 'UPDATE', 'DELETE', 'DROP', 'CREATE', 'ALTER')):
190
+ with _get_engine().connect() as conn:
191
+ r = conn.execute(a.text(d[0]), d[1])
192
+ conn.commit()
193
+ t_exec = time.perf_counter() - t1
194
+ print(f"[耗时] Schema: {t_schema:.2f}s | AI: {t_ai:.2f}s | 执行: {t_exec:.2f}s")
195
+ print(f"[SQL] {_fill_sql(d[0], d[1])}")
196
+ return f"影响了 {r.rowcount} 行"
197
+ result = b.read_sql(
198
+ sql=a.text(d[0]),
199
+ con=_get_engine(),
200
+ params=d[1]
201
+ )
202
+ t_exec = time.perf_counter() - t1
203
+ print(f"[耗时] Schema: {t_schema:.2f}s | AI: {t_ai:.2f}s | 执行: {t_exec:.2f}s")
204
+ print(f"[SQL] {_fill_sql(d[0], d[1])}")
205
+ return result
206
+ def main():
207
+ _load_config()
208
+ while 1:
209
+ result = sql(input(""))
210
+ print(result)
211
+
212
+ if __name__ == "__main__":
213
+ main()
214
+