diff --git a/README.md b/README.md index 3aba1e4..61fe990 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,11 @@ logger = logging.getLogger(__name__) class MyPlugin(Plugin): type = "my_plugin" - db_name = "my_plugin.db" # 自动在 data_dir 下建 SQLite 库;不需要则置 None + db_name = "my_plugin.db" # SQLite:自动在 data_dir 下建库;config 含 database.url 时优先走 PostgreSQL;不需要则置 None auto_bind = True # 首次初始化时自动 auto_bind_models(self.type) async def on_init(self): - # 数据库已由基类自动初始化(self.db) + # 数据库已由基类自动初始化(self.db,SQLite 或 PostgreSQL) self.set_var("db", self.db) # 写入插件 global_var 模块 # self.spawn_task(self._background_loop()) # 后台任务(cleanup 自动取消) # self.register_cleanup(self._close_client) # 注册清理钩子(逆序执行) @@ -59,6 +59,15 @@ class MyPlugin(Plugin): ... +# PostgreSQL 接入(可选):在插件 config.toml 配置 database 段后, +# 基类 init_database() 检测到 url 即走 Database.from_config 接入 PG:: +# +# # config/workers/my_plugin/config.toml +# [workers.config.database] +# type = "postgresql" +# url = "postgresql+asyncpg://user:pwd@127.0.0.1:5432/mydb" +# generate_schemas = true + # 暴露给 app lifespan 的模块级入口(bound method,签名与旧写法一致) plugin = MyPlugin() create_workers = plugin.create_workers @@ -70,7 +79,7 @@ cleanup = plugin.cleanup | 属性 / 方法 | 说明 | | -------------- | ---------------------------------------------------------------------------- | | `type` | 插件类型(worker type),匹配 subject 前缀与 `config/workers/`(必填) | -| `db_name` | 默认 SQLite 文件名(放 `data_dir` 下);`None` 表示不自动建库 | +| `db_name` | 默认 SQLite 文件名(放 `data_dir` 下);config 含 `database.url` 时优先走 PostgreSQL;`None` 表示不自动建库 | | `auto_bind` | 首次初始化时是否自动 `auto_bind_models(self.type)` | | `on_init()` | 首次 `create_workers` 时调用一次,初始化资源 | | `on_cleanup()` | `cleanup` 时调用,清理自定义资源 | @@ -79,7 +88,7 @@ cleanup = plugin.cleanup - **幂等**:重复 `create_workers` 只初始化一次(`asyncio.Lock` 守卫,并发调用安全) - **统一日志**:`logger` 自动带 `[type]` 前缀 -- **数据库**:`init_database()` 统一建路径、建表、记录,`cleanup` 自动 `dispose()` +- **数据库**:`init_database()` 统一建库建表、记录,`cleanup` 自动 `dispose()`(SQLite 按 `db_name`;config 含 `database.url` 时走 PostgreSQL) - **global_var**:`set_var()` / `get_var()` 统一读写插件 `global_var` 模块;`cleanup` 时自动将已发布的变量重置为 `None`,避免业务代码拿到已释放的资源 - **后台任务**:`spawn_task()` 跟踪,`cleanup` 时自动取消并 `gather` - **清理钩子**:`register_cleanup()` 注册,`cleanup` 时逆序执行 diff --git a/pyproject.toml b/pyproject.toml index ad11146..c1486cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = ["hatchling"] [project] name = "chongming-worker" -version = "0.1.0" +version = "0.2.0" description = "chongming worker" readme = "README.md" requires-python = ">=3.12" diff --git a/src/chongming_worker/plugin.py b/src/chongming_worker/plugin.py index 3f026cd..bc49fe9 100644 --- a/src/chongming_worker/plugin.py +++ b/src/chongming_worker/plugin.py @@ -18,11 +18,11 @@ Plugin 基类 — 统一 Worker 插件初始化与清理生命周期 class MyPlugin(Plugin): type = "myname" - db_name = "myname.db" # 自动在 data_dir 下建库;不需要则置 None + db_name = "myname.db" # SQLite:自动在 data_dir 下建库;config 含 database.url 时优先走 PostgreSQL;不需要则置 None auto_bind = True # 首次初始化时自动 auto_bind_models("myname") async def on_init(self): - # 数据库已由基类 _bootstrap 初始化(self.db) + # 数据库已由基类 _bootstrap 初始化(self.db,SQLite 或 PostgreSQL) self.set_var("db", self.db) # 需要后台任务时: # self.spawn_task(self._background_loop()) @@ -31,6 +31,15 @@ Plugin 基类 — 统一 Worker 插件初始化与清理生命周期 # 关闭自定义客户端等(后台任务与数据库由基类统一处理) ... + # PostgreSQL 接入(可选):在插件 config.toml 配置 database 段后, + # 基类 init_database() 检测到 url 即走 Database.from_config 接入 PG:: + # + # # config/workers//config.toml + # [workers.config.database] + # type = "postgresql" + # url = "postgresql+asyncpg://user:pwd@127.0.0.1:5432/mydb" + # generate_schemas = true + # 暴露给 app lifespan 的模块级入口(bound method,签名与旧写法一致) plugin = MyPlugin() create_workers = plugin.create_workers @@ -39,7 +48,8 @@ Plugin 基类 — 统一 Worker 插件初始化与清理生命周期 框架自动处理: - 幂等:重复 ``create_workers`` 只会初始化一次 - 统一日志:``logger`` 自动带 ``[type]`` 前缀 - - 数据库:``_bootstrap()`` / ``init_database()`` 统一建路径、建表、记录,``cleanup`` 自动 dispose + - 数据库:``_bootstrap()`` / ``init_database()`` 统一建库建表、记录,``cleanup`` 自动 dispose + (SQLite 按 ``db_name`` 建库;config 含 ``database.url`` 时优先走 PostgreSQL) - global_var:``set_var()`` 写入插件自己的 ``global_var`` 模块(白名单由插件决定) - 后台任务:``spawn_task()`` 跟踪,``cleanup`` 时自动取消 - 清理钩子:``register_cleanup()`` 注册,``cleanup`` 时逆序执行 @@ -70,7 +80,7 @@ class Plugin: #: 插件类型(worker type),必须被子类覆盖 type: str = "default" - #: 默认数据库文件名(data_dir 下);None 表示不自动建库 + #: 默认 SQLite 文件名(data_dir 下);config 含 database.url 时优先走 PostgreSQL;None 表示不自动建库 db_name: str | None = None #: 首次初始化时自动调用 auto_bind_models auto_bind: bool = True @@ -94,11 +104,11 @@ class Plugin: # ------------------------------------------------------------------ async def _bootstrap(self) -> None: """ - 默认资源引导:按 db_name 初始化数据库。 + 默认资源引导:按 db_name(SQLite)或 database.url(PostgreSQL)初始化数据库。 由基类在 on_init 之前自动调用;子类需要完全自定义建库逻辑时覆写此方法。 """ - if self.db_name: + if self.db_name or self._database_config().get("url"): await self.init_database(self.db_name) async def on_init(self) -> None: @@ -195,15 +205,38 @@ class Plugin: # ------------------------------------------------------------------ # 辅助方法 # ------------------------------------------------------------------ + def _database_config(self) -> dict: + """提取运行时配置中的 database 段(不存在或非 dict 时返回空 dict)。""" + db_conf = self.config.get("database") if isinstance(self.config, dict) else None + if isinstance(db_conf, dict): + return db_conf + return {} + async def init_database(self, filename: str | None = None) -> Any: """ - 在 data_dir 下初始化 SQLite 数据库,返回 Database 实例并存入 self.db。 + 初始化数据库并存入 self.db,支持两种后端: + + - PostgreSQL:config 含 ``database.url``(如插件 config.toml 的 + ``[workers.config.database]`` 配置 type/url)时优先走 ``Database.from_config``; + - SQLite:否则在 data_dir 下按文件名建库(现有插件零改动)。 Args: - filename: 数据库文件名(默认取 self.db_name) + filename: SQLite 数据库文件名(默认取 self.db_name) """ from chongming_database import Database + db_conf = self._database_config() + if db_conf.get("url"): + db = Database.from_config(db_conf) + await db.initialize( + generate_schemas=db_conf.get("generate_schemas", True) + ) + self.db = db + self.logger.info( + "[%s] 数据库已初始化 (PostgreSQL): %s", self.type, db_conf["url"] + ) + return db + filename = filename or self.db_name if not filename: raise ValueError("未指定数据库文件名(请设置 db_name 或传入 filename)")