55from dataclasses import dataclass
66from typing import Annotated , Literal
77
8+ import anyio
89import cappa
910import granian
1011
1112from cappa .output import error_format
1213from rich .panel import Panel
13- from rich .prompt import IntPrompt
14+ from rich .prompt import IntPrompt , Prompt
1415from rich .table import Table
1516from rich .text import Text
1617from sqlalchemy import text
2021from backend .common .enums import DataBaseType , PrimaryKeyType
2122from backend .common .exception .errors import BaseExceptionError
2223from backend .core .conf import settings
23- from backend .database .db import async_db_session
24+ from backend .core .path_conf import BASE_PATH
25+ from backend .database .db import async_db_session , create_tables , drop_tables
26+ from backend .database .redis import redis_client
2427from backend .plugin .tools import get_plugin_sql , get_plugins
2528from backend .utils ._await import run_await
2629from backend .utils .console import console
@@ -37,6 +40,63 @@ def __init__(self) -> None:
3740 super ().__init__ (extra_extensions = ['.json' , '.yaml' , '.yml' ])
3841
3942
43+ async def init () -> None :
44+ panel_content = Text ()
45+ panel_content .append ('【数据库配置】' , style = 'bold green' )
46+ panel_content .append ('\n \n • 类型: ' )
47+ panel_content .append (f'{ settings .DATABASE_TYPE } ' , style = 'yellow' )
48+ panel_content .append ('\n • 数据库:' )
49+ panel_content .append (f'{ settings .DATABASE_SCHEMA } ' , style = 'yellow' )
50+ panel_content .append ('\n • 主键模式:' )
51+ panel_content .append (
52+ f'{ settings .DATABASE_PK_MODE } ' ,
53+ style = 'yellow' ,
54+ )
55+ pk_details = panel_content .from_markup (
56+ '[link=https://fastapi-practices.github.io/fastapi_best_architecture_docs/backend/reference/pk.html](了解详情)[/]'
57+ )
58+ panel_content .append (pk_details )
59+ panel_content .append ('\n \n 【Redis 配置】' , style = 'bold green' )
60+ panel_content .append ('\n \n • 数据库:' )
61+ panel_content .append (f'{ settings .REDIS_DATABASE } ' , style = 'yellow' )
62+ plugins = get_plugins ()
63+ panel_content .append ('\n \n 【已安装插件】' , style = 'bold green' )
64+ panel_content .append ('\n \n • ' )
65+ if plugins :
66+ panel_content .append (f'{ ", " .join (plugins )} ' , style = 'yellow' )
67+ else :
68+ panel_content .append ('无' , style = 'dim' )
69+
70+ console .print (Panel (panel_content , title = f'fba v{ __version__ } 初始化' , border_style = 'cyan' , padding = (1 , 2 )))
71+ ok = Prompt .ask (
72+ '即将[red]重建数据库表[/red]并[red]执行所有 SQL 脚本[/red],确认继续吗?' , choices = ['y' , 'n' ], default = 'n'
73+ )
74+
75+ if ok .lower () == 'y' :
76+ console .print ('开始初始化...' , style = 'white' )
77+ try :
78+ console .print ('丢弃数据库表' , style = 'white' )
79+ await drop_tables ()
80+ console .print ('丢弃 Redis 缓存' , style = 'white' )
81+ await redis_client .delete_prefix (settings .JWT_USER_REDIS_PREFIX )
82+ await redis_client .delete_prefix (settings .TOKEN_EXTRA_INFO_REDIS_PREFIX )
83+ await redis_client .delete_prefix (settings .TOKEN_REDIS_PREFIX )
84+ await redis_client .delete_prefix (settings .TOKEN_REFRESH_REDIS_PREFIX )
85+ console .print ('创建数据库表' , style = 'white' )
86+ await create_tables ()
87+ console .print ('执行 SQL 脚本' , style = 'white' )
88+ sql_scripts = await get_sql_scripts ()
89+ for sql_script in sql_scripts :
90+ console .print (f'正在执行:{ sql_script } ' , style = 'white' )
91+ await execute_sql_scripts (sql_script , is_init = True )
92+ console .print ('初始化成功' , style = 'green' )
93+ console .print ('\n 快试试 [bold cyan]fba run[/bold cyan] 启动服务吧~' )
94+ except Exception as e :
95+ raise cappa .Exit (f'初始化失败:{ e } ' , code = 1 )
96+ else :
97+ console .print ('已取消初始化' , style = 'yellow' )
98+
99+
40100def run (host : str , port : int , reload : bool , workers : int ) -> None : # noqa: FBT001
41101 url = f'http://{ host } :{ port } '
42102 docs_url = url + settings .FASTAPI_DOCS_URL
@@ -122,26 +182,52 @@ async def install_plugin(
122182 raise cappa .Exit ('path 和 repo_url 不能同时指定' , code = 1 )
123183
124184 plugin_name = None
125- console .print (Text ( '开始安装插件...' , style = 'bold cyan' ) )
185+ console .print ('开始安装插件...' , style = 'bold cyan' )
126186
127187 try :
128188 if path :
129189 plugin_name = await install_zip_plugin (file = path )
130190 if repo_url :
131191 plugin_name = await install_git_plugin (repo_url = repo_url )
132192
133- console .print (Text ( f'插件 { plugin_name } 安装成功' , style = 'bold green' ) )
193+ console .print (f'插件 { plugin_name } 安装成功' , style = 'bold green' )
134194
135195 sql_file = await get_plugin_sql (plugin_name , db_type , pk_type )
136196 if sql_file and not no_sql :
137- console .print (Text ( '开始自动执行插件 SQL 脚本...' , style = 'bold cyan' ) )
197+ console .print ('开始自动执行插件 SQL 脚本...' , style = 'bold cyan' )
138198 await execute_sql_scripts (sql_file )
139199
140200 except Exception as e :
141201 raise cappa .Exit (e .msg if isinstance (e , BaseExceptionError ) else str (e ), code = 1 )
142202
143203
144- async def execute_sql_scripts (sql_scripts : str ) -> None :
204+ async def get_sql_scripts () -> list [str ]:
205+ sql_scripts = []
206+ db_dir = (
207+ BASE_PATH / 'sql' / 'mysql'
208+ if DataBaseType .mysql == settings .DATABASE_TYPE
209+ else BASE_PATH / 'sql' / 'postgresql'
210+ )
211+ main_sql_file = (
212+ db_dir / 'init_test_data.sql'
213+ if PrimaryKeyType .autoincrement == settings .DATABASE_PK_MODE
214+ else db_dir / 'init_snowflake_test_data.sql'
215+ )
216+
217+ main_sql_path = anyio .Path (main_sql_file )
218+ if await main_sql_path .exists ():
219+ sql_scripts .append (str (main_sql_file ))
220+
221+ plugins = get_plugins ()
222+ for plugin in plugins :
223+ plugin_sql = await get_plugin_sql (plugin , settings .DATABASE_TYPE , settings .DATABASE_PK_MODE )
224+ if plugin_sql :
225+ sql_scripts .append (str (plugin_sql ))
226+
227+ return sql_scripts
228+
229+
230+ async def execute_sql_scripts (sql_scripts : str , * , is_init : bool = False ) -> None :
145231 async with async_db_session .begin () as db :
146232 try :
147233 stmts = await parse_sql_script (sql_scripts )
@@ -150,7 +236,8 @@ async def execute_sql_scripts(sql_scripts: str) -> None:
150236 except Exception as e :
151237 raise cappa .Exit (f'SQL 脚本执行失败:{ e } ' , code = 1 )
152238
153- console .print (Text ('SQL 脚本已执行完成' , style = 'bold green' ))
239+ if not is_init :
240+ console .print ('SQL 脚本已执行完成' , style = 'bold green' )
154241
155242
156243async def import_table (
@@ -202,7 +289,7 @@ def generate() -> None:
202289 except Exception as e :
203290 raise cappa .Exit (e .msg if isinstance (e , BaseExceptionError ) else str (e ), code = 1 )
204291
205- console .print (Text ( '\n 代码已生成完毕' , style = 'bold green' ) )
292+ console .print ('\n 代码已生成完毕' , style = 'bold green' )
206293 console .print (Text ('\n 详情请查看:' ), Text (gen_path , style = 'bold magenta' ))
207294
208295
@@ -352,13 +439,16 @@ def __call__(self) -> None:
352439@cappa .command (help = '一个高效的 fba 命令行界面' , default_long = True )
353440@dataclass
354441class FbaCli :
442+ init : Annotated [bool , cappa .Arg (default = False , show_default = False , help = '初始化 fba 项目' )]
355443 sql : Annotated [
356444 str ,
357445 cappa .Arg (value_name = 'PATH' , default = '' , show_default = False , help = '在事务中执行 SQL 脚本' ),
358446 ]
359447 subcmd : cappa .Subcommands [Run | Celery | Add | CodeGenerator | None ] = None
360448
361449 async def __call__ (self ) -> None :
450+ if self .init :
451+ await init ()
362452 if self .sql :
363453 await execute_sql_scripts (self .sql )
364454
0 commit comments