Package: graia-saya
Sayaa modular implementation with modern design and injection不仅仅是模块化, 就如炭之魔女不仅仅只有追求心上人的一面.Installationpipinstallgraia-saya或者使用poetry:poetryaddgraia-saya架构简述Saya 的架构分为这几大块:Saya Controller(控制器),Module Channel(模块容器),Cube(内容容器),Schema(元信息模板),Behaviour(行为).Saya Controller: 负责控制各个模块, 分配Channel, 管理模块启停,Behaviour的注册和调用.Module Channel: 负责对模块服务, 收集模块的各式信息, 像 模块的名称, 作者, 长段的描述 之类,
并负责包装模块的内容为Cube, 用以Behaviour对底层接口的操作.Cube: 对模块提供的内容附加一个由Schema实例化来的metadata, 即 "元信息", 用于给Behaviour进行处理.Schema: 用于给模块提供的内容附加不同类型的元信息, 给Behaviourisinstance处理用.Behaviour: 根据Cube及其元信息, 对底层接口(例如Broadcast,Scheduler等)进行操作.
包括allocate与uninstall两个操作.使用安装后, 在编辑器内打开工作区, 创建如下的目录结构:这里我们建立的是 Real World 中的, 最简且最易于扩展, 维护的示例性目录结构.Saya中, 我们的导入机制复用了 Python 自身的模块和包机制, 理论上只需要符合 Python 的导入规则,
就能引入模块到实例中./home/elaina/saya-example
│.gitignore
│main.py
│pyproject.toml
│
└─modules│__init__.py│module_as_file.py# 作为文件的合法模块可以被调用.│└─module_as_dir# 作为文件夹的合法模块可以被调用(仅调用 __init__.py 下的内容).__init__.pySaya需要一个入口(entry), 用于创建Controller, 并让Controller分配Channel给这之后被Saya.require方法引入的模块.
在我们提供的目录结构中,main.py将作为入口文件, 被 Python 解释器首先执行.入口文件的编写首先, 我们需要引入Saya,Broadcast, 还有其内部集成的对Broadcast的支持:fromgraia.sayaimportSayafromgraia.broadcastimportBroadcastfromgraia.saya.builtins.broadcastimportBroadcastBehaviour分别创建Broadcast,Saya的实例:importasyncioloop=asyncio.get_event_loop()broadcast=Broadcast(loop=loop)saya=Saya(broadcast)# 这里可以置空, 但是会丢失 Lifecycle 特性创建BroadcastBehaviour的实例, 并将其注册到现有的Saya实例中:saya.install_behaviours(BroadcastBehaviour(broadcast))为了导入各个模块,Saya Controller需要先进入上下文:withsaya.module_context():...引入各个模块, 这里的模块目前都需要手动引入, 后期可能会加入配置系统:withsaya.module_context():saya.require("modules.module_as_file")saya.require("modules.module_as_dir")这里使用传统方式启动asyncio的事件循环.不同框架有不同的启动方式, 比如 Avilla 使用了 Launch Component, 这里仅做演示.try:loop.run_forever()exceptKeyboardInterrupt:exit()或者也可以这样:asyncdefdo_nothing():passloop.run_until_complete(do_nothing())最终的结果:importasynciofromgraia.sayaimportSayafromgraia.broadcastimportBroadcastfromgraia.saya.builtins.broadcastimportBroadcastBehaviourloop=asyncio.get_event_loop()broadcast=Broadcast(loop=loop)saya=Saya(broadcast)saya.install_behaviours(BroadcastBehaviour(broadcast))withsaya.module_context():saya.require("modules.module_as_file")saya.require("modules.module_as_dir")try:loop.run_forever()exceptKeyboardInterrupt:exit()就这样, 一个入口文件就这样完成了, 现在主要是插件部分.第一次来到module_as_file.py:fromgraia.sayaimportSaya,Channelsaya=Saya.current()channel=Channel.current()两个currnet方法的调用, 访问了Saya实例和当前上下文分配的Channel.接下来, 导入ListenerSchema:fromgraia.saya.builtins.broadcast.schemaimportListenerSchemaListenerSchema作为Schema, 标识相对应的模块内容为一Listener,
并在模块被导入后经由Behaviour进行操作.使用Channel.use方法, 向Channel提供内容:@channel.use(ListenerSchema(listening_events=[...]# 填入你需要监听的事件))asyncdefmodule_listener():print("事件被触发!!!!")然后, 引入结束,module_as_file.py文件内容如下, 这里我们监听SayaModuleInstalled事件, 作为Lifecycle API的简单示例:fromgraia.sayaimportSaya,Channelfromgraia.saya.builtins.broadcast.schemaimportListenerSchemafromgraia.saya.eventimportSayaModuleInstalledsaya=Saya.current()channel=Channel.current()@channel.use(ListenerSchema(listening_events=[SayaModuleInstalled]))asyncdefmodule_listener(event:SayaModuleInstalled):print(f"{event.module}::模块加载成功!!!")我们对modules/module_as_dir/__init__.py也如法炮制, copy 上方的代码, 进入虚拟环境, 然后运行main.py.root@localhost:# python main.py2021-02-1601:19:56.632|DEBUG|graia.saya:require:58-requiremodules.module_as_file2021-02-1601:19:56.639|DEBUG|graia.saya:require:58-requiremodules.module_as_dir
modules.module_as_file::模块加载成功!!!
modules.module_as_file::模块加载成功!!!
modules.module_as_dir::模块加载成功!!!
modules.module_as_dir::模块加载成功!!!Factorysaya.factory提供了factory与buffer_modifier两个装饰器, 用于进一步构建自定义的装饰器来构造用于Channel.use的Schema.以ListenerSchema为例：fromgraia.saya.factoryimportfactory@factorydeflisten(*event)->SchemaWrapper:defwrapper(func:Callable,buffer:Dict[str,Any])->ListenerSchema:buffer["inline_dispatchers"]=buffer.pop("dispatchers",[])returnListenerSchema(listening_events=list(event),**buffer)returnwrapper再将其装饰在响应的函数上：fromgraia.saya.builtins.broadcast.shortcutimportlisten@listen(...)# 填入你需要监听的事件asyncdefmodule_listener():print("事件被触发!!!!")协议本项目使用 MIT 作为开源协议.