├── .gitignore ├── nonebot_plugin_flexperm ├── util.py ├── defaults.yml ├── config.py ├── __init__.py ├── adapters.py ├── check.py ├── cmds.py ├── __init__.pyi ├── plugin.py └── core.py ├── docs ├── adapters.md ├── command.md ├── permdesc.md └── interface.md ├── pyproject.toml ├── .github └── workflows │ └── publish.yml ├── LICENSE ├── README.md └── poetry.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /venv/ 3 | *.pyc 4 | /dist/ 5 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/util.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | 4 | def try_int(s: str) -> Union[str, int]: 5 | try: 6 | return int(s) 7 | except ValueError: 8 | return s 9 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/defaults.yml: -------------------------------------------------------------------------------- 1 | anyone: {} 2 | 3 | private: {} 4 | 5 | group: {} 6 | 7 | group_admin: {} 8 | 9 | group_owner: 10 | inherits: 11 | - global:group_admin 12 | 13 | superuser: 14 | permissions: 15 | - flexperm.* 16 | -------------------------------------------------------------------------------- /docs/adapters.md: -------------------------------------------------------------------------------- 1 | # 协议支持情况 2 | 3 | 以下列出目前支持的协议。其他协议可以在[这里](../nonebot_plugin_flexperm/adapters.py)添加适配。 4 | 5 | ## [OneBot V11](https://onebot.adapters.nonebot.dev/) 6 | 7 | 理论上全部支持,~~但我没测试,因为我的 bot 炸了()~~ 8 | 9 | ## [开黑啦](https://github.com/Tian-que/nonebot-adapter-kaiheila) 10 | 11 | 支持私信和频道,暂不支持基于身份(群主、管理员)的权限设置。 12 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import nonebot 4 | from pydantic import BaseModel 5 | 6 | 7 | class Config(BaseModel): 8 | flexperm_base: Path = Path('permissions') 9 | flexperm_debug_check: bool = False 10 | flexperm_default_adapter: str = 'onebot' 11 | 12 | 13 | c = Config(**nonebot.get_driver().config.dict()) 14 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/__init__.py: -------------------------------------------------------------------------------- 1 | from nonebot.plugin.manager import PluginLoader 2 | 3 | # noinspection PyUnresolvedReferences 4 | if type(__loader__) is not PluginLoader: 5 | raise TypeError('Do not import flexperm directly. Use "nonebot.require".') 6 | 7 | from . import plugin as _ 8 | from . import cmds as _ 9 | 10 | from .plugin import register, PluginHandler 11 | 12 | del PluginLoader 13 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "nonebot-plugin-flexperm" 3 | version = "0.7.0" 4 | description = "精细化的 NoneBot 权限管理插件" 5 | license = "MIT" 6 | authors = ["Muchan "] 7 | readme = "README.md" 8 | repository = "https://github.com/rmuchan/nonebot-plugin-flexperm" 9 | keywords = ["nonebot"] 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.8" 13 | nonebot2 = "^2.0.0" 14 | "ruamel.yaml" = "^0.17.10" 15 | nonebot-plugin-apscheduler = "^0.2.0" 16 | 17 | [tool.poetry.group.dev.dependencies] 18 | nonebot-adapter-onebot = "^2.2.3" 19 | 20 | [build-system] 21 | requires = ["poetry-core>=1.0.0"] 22 | build-backend = "poetry.core.masonry.api" 23 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | publish: 8 | name: Publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Install poetry 15 | run: pipx install poetry 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v4 19 | with: 20 | python-version: '3.8' 21 | cache: 'poetry' 22 | 23 | - name: Install dependencies 24 | run: poetry install 25 | 26 | - name: Build package 27 | run: poetry build 28 | 29 | - name: Publish package 30 | uses: pypa/gh-action-pypi-publish@release/v1 31 | with: 32 | user: __token__ 33 | password: ${{ secrets.PYPI_API_TOKEN }} 34 | 35 | - name: Push tag 36 | run: | 37 | git tag "v$(poetry version -s)" 38 | git push --tags -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Muchan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/command.md: -------------------------------------------------------------------------------- 1 | # 命令文档 2 | 3 | 与 NoneBot2 的默认行为不同,本插件的所有命令(无参数的除外)要求命令名和参数之间有空格隔开。 4 | 5 | ## /flexperm.reload 6 | 7 | 重新加载权限配置。 8 | 9 | 如果有通过命令或接口进行的修改尚未保存则会拒绝重新加载,可以使用`force`参数忽略这一检查。 10 | 11 | 用法:`/flexperm.reload [force]` 12 | 13 | 需要权限:`flexperm.reload` 14 | 15 | ## /flexperm.save 16 | 17 | 立即保存权限配置,用于通过命令或接口修改过配置之后。即使不使用这个命令,配置也会定期自动保存。 18 | 19 | 需要权限:`flexperm.reload` 20 | 21 | ## /flexperm.add 22 | 23 | 添加权限描述。 24 | 25 | 如果没有提供权限组指示符,则默认使用当前会话所代表的权限组。详见[权限组指示符](interface.md#权限组指示符)。 26 | 27 | **不会**自动[修饰](interface.md#权限名称修饰)权限名,需要填入目标权限的正式名称。 28 | 29 | 用法:`/flexperm.add [权限组指示符] 权限描述` 30 | 31 | 需要权限:`flexperm.edit.perm` 32 | 33 | ## /flexperm.remove 34 | 35 | 移除权限描述。 36 | 37 | **不会**自动[修饰](interface.md#权限名称修饰)权限名,需要填入目标权限的正式名称。 38 | 39 | 用法:`/flexperm.remove [权限组指示符] 权限描述` 40 | 41 | 需要权限:`flexperm.edit.perm` 42 | 43 | ## /flexperm.addinh 44 | 45 | 添加继承关系。 46 | 47 | 用法:`/flexperm.addinh [权限组指示符] 继承权限组的指示符` 48 | 49 | 需要权限:`flexperm.edit.inherit` 50 | 51 | ## /flexperm.rminh 52 | 53 | 移除继承关系。 54 | 55 | 用法:`/flexperm.rminh [权限组指示符] 继承权限组的指示符` 56 | 57 | 需要权限:`flexperm.edit.inherit` 58 | 59 | ## /flexperm.addgrp 60 | 61 | 添加权限组。 62 | 63 | 用法:`/flexperm.addgrp [权限组指示符]` 64 | 65 | 需要权限:`flexperm.edit.group` 66 | 67 | ## /flexperm.rmgrp 68 | 69 | 移除权限组,权限组必须为空。 70 | 71 | 用法:`/flexperm.rmgrp [权限组指示符]` 72 | 73 | 需要权限:`flexperm.edit.group` 74 | 75 | ## /flexperm.rmgrpf 76 | 77 | 移除权限组,权限组可以非空。 78 | 79 | 用法:`/flexperm.rmgrpf [权限组指示符]` 80 | 81 | 需要权限:`flexperm.edit.group.force` 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nonebot-plugin-flexperm 2 | 3 | 精细化的 NoneBot 权限管理插件。 4 | 5 | 提供对用户精确到人或群、对插件精确到指令或更细粒度的权限管理功能。 6 | 7 | ## 安装 8 | 9 | - 使用 nb-cli 10 | 11 | ```shell 12 | nb plugin install nonebot-plugin-flexperm 13 | ``` 14 | 15 | - 使用 poetry 16 | 17 | ```shell 18 | poetry add nonebot-plugin-flexperm 19 | ``` 20 | 21 | - 使用 pip 22 | 23 | ```shell 24 | pip install nonebot-plugin-flexperm 25 | ``` 26 | 27 | ## 依赖 28 | 29 | 依赖`nonebot^2.0.0`,支持的协议见[这里](docs/adapters.md)。 30 | 31 | ## 使用 32 | 33 | 本插件主要通过 NoneBot 的 require 机制向**其他插件**提供功能。本插件也提供了一组命令,用于直接管理权限配置。 34 | 35 | ```python 36 | from nonebot import require 37 | require("nonebot_plugin_flexperm") 38 | from nonebot_plugin_flexperm import register 39 | P = register("my_plugin") 40 | ``` 41 | 42 | `P`是一个可调用对象,以权限名为参数调用即可得到相应的检查器。`P`的其他接口详见[接口文档](docs/interface.md)。 43 | 44 | ```python 45 | from nonebot import on_command 46 | cmd = on_command("my_command", permission=P("my_command")) 47 | 48 | @cmd.handle() 49 | async def _(bot, event): 50 | ... 51 | ``` 52 | 53 | 这样,运行时只有具有`my_plugin.my_command`权限的用户或群才能使用该命令。 54 | 55 | ### 权限配置文件 56 | 57 | 权限配置文件使用 YAML 格式,详见[权限配置文档](docs/permdesc.md)。示例: 58 | 59 | ```yaml 60 | anyone: 61 | permissions: 62 | - my_plugin.help 63 | 64 | group_admin: 65 | permissions: 66 | - my_plugin.my_command 67 | - another_plugin.* 68 | - -another_plugin.another_command 69 | ``` 70 | 71 | 这个配置文件授予了所有用户`my_plugin.help`权限,同时授予了群管理员`my_plugin.my_command`权限和`another_plugin`下的所有子权限,但撤销`another_plugin.another_command`权限。 72 | 73 | ### 命令 74 | 75 | 权限配置文件可以在运行时修改,然后使用`/flexperm.reload`命令重新加载。 76 | 77 | 也可以通过命令编辑权限配置,详见[命令文档](docs/command.md)。 78 | 79 | ## 配置 80 | 81 | 本插件使用3个配置项,均为可选。如需修改,写入 NoneBot 项目环境文件`.env.*`即可。 82 | 83 | - `flexperm_base`: 权限配置文件所在目录,默认为`permissions`。 84 | - `flexperm_debug_check`: 是否输出检查权限过程中的调试信息,默认为`false`。未启用 NoneBot 的调试模式时无效。 85 | - `flexperm_default_adapter`: 检查基于用户ID的权限配置时的默认适配器名,不区分大小写,默认为`onebot`。 86 | 87 | ## 鸣谢 88 | 89 | - [nonebot / nonebot2](https://github.com/nonebot/nonebot2) 90 | - [Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 91 | -------------------------------------------------------------------------------- /docs/permdesc.md: -------------------------------------------------------------------------------- 1 | # 权限配置文档 2 | 3 | ## 概述 4 | 5 | 权限配置文件使用 YAML 格式,每个文件在本插件中被称为一个**名称空间**。不同名称空间里可以有同名的权限组,同一个名称空间下的权限组名必须唯一。 6 | 7 | 本插件会从插件配置项`flexperm_base`指定的路径(默认为`permissions`目录)加载权限配置,这个目录下每个`.yml`文件对应一个名称空间,文件名(不含扩展名)就是这个名称空间的名字。本插件也会加载其他插件注册时指定的[预设权限配置](interface.md#preset),这种情况下,配置文件会被加载到与插件同名的名称空间,无关于文件名。 8 | 9 | 每个文件的顶层对象应为字典类型,键为权限组名,值为权限组的描述。权限组名一般应为字符串。对于`group`和`user`名称空间,[默认适配器](../README.md#配置)的用户ID或群号可以使用整数,其他适配器需要用格式为`<适配器名小写>:<用户ID/群号>`的字符串。 10 | 11 | 权限组的描述应为字典类型,可以包含两个字段——`permissions`和`inherits`。两个字段都为可选,若未提供,则等价于设为空列表。 12 | 13 | ## 权限描述 14 | 15 | 每个权限组的描述中,`permissions`字段指定该组包含的权限描述,应为列表,元素应为字符串。每个元素是一项权限描述。 16 | 17 | 权限描述由两部分组成,中间没有分隔符。 18 | 19 | 第一部分指定是授予还是撤销权限。默认为授予,减号(`-`)代表撤销。 20 | 21 | 第二部分指定要授予或撤销的权限项目。包含由点(`.`)分隔的若干段,每段可以包含字母、数字和下划线,且不应以数字开头。最后一段也可以是单独一个星号(`*`)。若最后一段是星号,则指定具体权限及其所有子权限,否则单独指定具体权限。例如: 22 | 23 | | 权限描述 | 含义 | 24 | | :------- | :---------------------------------------- | 25 | | `a.b` | 授予`a.b`权限 | 26 | | `-c.d` | 撤销`c.d`权限 | 27 | | `e.*` | 授予`e`及其所有子权限,如`e.a`、`e.b.c`等 | 28 | | `-f.g.*` | 撤销`f.g`及其所有子权限 | 29 | 30 | 说明:授予一个权限时不会自动授予其路径上的权限,如权限描述`a.b.c`只会授予`a.b.c`权限,而不会同时授予`a`和`a.b`两个权限。撤销同理。 31 | 32 | 需要授予全部权限时,由于星号在 YAML 语法中有特殊意义,权限描述须写为`"*"`。其他情况下可以不加引号。 33 | 34 | ## 继承 35 | 36 | 每个权限组的描述中,`inherits`字段指定本权限组继承的权限组,应为列表,元素应为字符串。每个元素标识一个其他权限组。 37 | 38 | 权限组标识可以包含一个冒号,冒号前为目标权限组的名称空间,冒号后为权限组名;也可以不包含冒号,代表当前名称空间,整个字符串为权限组名。 39 | 40 | ## 权限判断逻辑 41 | 42 | 对于每个权限组,首先检查指定权限在本权限组的`permissions`字段中是否被授予或撤销。若存在授予或撤销,则作为本组的检查结果(既被授予又被撤销时视为撤销)。若既未授予也未撤销,则递归地检查继承的组。在本组直接继承的所有组中,若某个组的检查结果为撤销,则本组检查结果也为撤销;若都没有撤销,且某个组的检查结果为授予,则本组检查结果也为授予;否则,检查无结果。 43 | 44 | 对于一个事件,本插件会依次检查下列权限组,以第一个有结果的为准。如果都无结果,则视为事件没有所需权限。 45 | 46 | - `user:<事件所属用户ID>` 47 | - `global:superuser`,如果用户是 bot 的超级用户 48 | - `global:anyone` 49 | - `global:group_admin`,如果事件来自群聊,且用户是群管理员(不含群主) 50 | - `global:group_owner`,如果事件来自群聊,且用户是群主 51 | - `group:<事件所属群号>`,如果事件来自群聊 52 | - `global:group`,如果事件来自群聊 53 | - `global:private`,如果事件来自私聊 54 | 55 | ## 默认权限组 56 | 57 | 本插件在`global`名称空间提供了六个默认组,定义如下: 58 | 59 | ```yaml 60 | anyone: {} 61 | private: {} 62 | group: {} 63 | group_admin: {} 64 | 65 | group_owner: 66 | inherits: 67 | - global:group_admin 68 | 69 | superuser: 70 | permissions: 71 | - flexperm.* 72 | ``` 73 | 74 | 前四个组均为空,后两个组各自带有一项默认设置。手动创建配置文件时需要把这两项设置加进去,除非需要改变对应的行为。 75 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/adapters.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from functools import cache 3 | from typing import ClassVar, Type, Union, Optional 4 | 5 | from nonebot import logger 6 | from nonebot.adapters import Event 7 | 8 | registry: dict[str, Type['AdapterHandler']] = {} 9 | 10 | 11 | class AdapterHandler(ABC): 12 | adapter: ClassVar[str] 13 | 14 | @abstractmethod 15 | def is_private_chat(self, event: Event) -> bool: ... 16 | 17 | @abstractmethod 18 | def get_group_id(self, event: Event) -> Union[int, str, None]: ... 19 | 20 | @abstractmethod 21 | def get_group_role(self, event: Event) -> Optional[str]: 22 | """ check "owner", "admin" """ 23 | 24 | def __init_subclass__(cls, **kwargs): 25 | super().__init_subclass__(**kwargs) 26 | if cls.adapter: 27 | if cls.adapter in registry: 28 | raise ValueError(f'Duplicate adapter handler for {cls.adapter}') 29 | registry[cls.adapter] = cls 30 | 31 | 32 | @cache 33 | def handler_for(adapter: str) -> AdapterHandler: 34 | cls = registry.get(adapter) 35 | if not cls: 36 | logger.warning('Unknown adapter {}', adapter) 37 | cls = Dummy 38 | return cls() 39 | 40 | 41 | class Dummy(AdapterHandler): 42 | adapter = '' 43 | 44 | def is_private_chat(self, event: Event) -> bool: 45 | return False 46 | 47 | def get_group_id(self, event: Event) -> Union[int, str, None]: 48 | return 49 | 50 | def get_group_role(self, event: Event) -> Optional[str]: 51 | return 52 | 53 | 54 | class OnebotV11(AdapterHandler): 55 | adapter = 'OneBot V11' 56 | 57 | def __init__(self): 58 | import nonebot.adapters.onebot.v11 as onebot 59 | self.onebot = onebot 60 | 61 | def is_private_chat(self, event: Event) -> bool: 62 | return isinstance(event, self.onebot.PrivateMessageEvent) 63 | 64 | def get_group_id(self, event: Event) -> Union[int, str, None]: 65 | if isinstance(event, self.onebot.GroupMessageEvent): 66 | return event.group_id 67 | 68 | def get_group_role(self, event: Event) -> Optional[str]: 69 | if isinstance(event, self.onebot.GroupMessageEvent): 70 | return event.sender.role 71 | 72 | 73 | class Kaiheila(AdapterHandler): 74 | adapter = 'Kaiheila' 75 | 76 | def __init__(self): 77 | import nonebot.adapters.kaiheila as kaiheila 78 | self.kaiheila = kaiheila 79 | 80 | def is_private_chat(self, event: Event) -> bool: 81 | return isinstance(event, self.kaiheila.event.PrivateMessageEvent) 82 | 83 | def get_group_id(self, event: Event) -> Union[int, str, None]: 84 | if isinstance(event, self.kaiheila.event.ChannelMessageEvent): 85 | return event.group_id 86 | 87 | def get_group_role(self, event: Event) -> Optional[str]: 88 | return 89 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/check.py: -------------------------------------------------------------------------------- 1 | from typing import Iterable, Tuple, Optional, Union 2 | 3 | from nonebot import logger 4 | from nonebot.adapters import Bot, Event 5 | 6 | from .adapters import handler_for 7 | from .config import c 8 | from .core import get, CheckResult, PermissionGroup 9 | from .util import try_int 10 | 11 | 12 | def check(bot: Bot, event: Event, perm: str) -> bool: 13 | if c.flexperm_debug_check: 14 | logger.debug('Checking {}', perm) 15 | for group in iterate_groups(bot, event): 16 | r = group.check(perm) 17 | if c.flexperm_debug_check: 18 | logger.debug('Got {} from {}', r, group) 19 | if r is not None: 20 | return r == CheckResult.ALLOW 21 | 22 | return False 23 | 24 | 25 | def get_permission_group_by_event(bot: Bot, event: Event) -> Optional[Tuple[str, Union[str, int]]]: 26 | h = handler_for(bot.adapter.get_name()) 27 | adapter = bot.adapter.get_name().split(maxsplit=1)[0].lower() 28 | is_default_adapter = (adapter == c.flexperm_default_adapter.lower()) 29 | 30 | if (group_id := h.get_group_id(event)) is not None: 31 | gn = group_id if is_default_adapter else f'{adapter}:{group_id}' 32 | return 'group', gn 33 | if h.is_private_chat(event): 34 | uid = event.get_user_id() 35 | un = try_int(uid) if is_default_adapter else f'{adapter}:{uid}' 36 | return 'user', un 37 | 38 | 39 | def iterate_groups(bot: Bot, event: Event) -> Iterable[PermissionGroup]: 40 | h = handler_for(bot.adapter.get_name()) 41 | adapter = bot.adapter.get_name().split(maxsplit=1)[0].lower() 42 | is_default_adapter = (adapter == c.flexperm_default_adapter.lower()) 43 | 44 | # 特定用户 45 | user_id = event.get_user_id() 46 | group = get('user', try_int(user_id)) if is_default_adapter else None 47 | if not (is_default_adapter and group.is_valid): 48 | group = get('user', f'{adapter}:{user_id}') 49 | yield group 50 | 51 | # Bot超级用户 52 | if is_superuser(bot, event): 53 | yield get('global', 'superuser') 54 | 55 | # 所有用户 56 | yield get('global', 'anyone') 57 | 58 | # 群组 59 | if (group_id := h.get_group_id(event)) is not None: 60 | # 用户在群组内的身份 61 | role = h.get_group_role(event) 62 | if role == 'admin': 63 | yield get('global', 'group_admin') 64 | elif role == 'owner': 65 | yield get('global', 'group_owner') 66 | 67 | # 特定群组 68 | group = get('group', try_int(group_id)) if is_default_adapter else None 69 | if not (is_default_adapter and group.is_valid): 70 | group = get('group', f'{adapter}:{group_id}') 71 | yield group 72 | 73 | # 所有群组 74 | yield get('global', 'group') 75 | 76 | # 私聊 77 | if h.is_private_chat(event): 78 | yield get('global', 'private') 79 | 80 | 81 | def is_superuser(bot: Bot, event: Event): 82 | try: 83 | user_id = event.get_user_id() 84 | except Exception: 85 | return False 86 | return ( 87 | f"{bot.adapter.get_name().split(maxsplit=1)[0].lower()}:{user_id}" in bot.config.superusers 88 | or user_id in bot.config.superusers 89 | ) 90 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/cmds.py: -------------------------------------------------------------------------------- 1 | from nonebot import CommandGroup 2 | from nonebot.adapters import Bot, Event, Message 3 | from nonebot.params import CommandArg, RawCommand 4 | from nonebot.typing import T_State 5 | from . import core 6 | from .plugin import register 7 | 8 | P = register('flexperm') 9 | 10 | cg = CommandGroup('flexperm', block=True, force_whitespace=True) 11 | 12 | 13 | def h(x): 14 | return x.handle() 15 | 16 | 17 | @h(cg.command('reload', permission=P('reload'))) 18 | async def _(bot: Bot, event: Event, arg: Message = CommandArg()): 19 | force = str(arg).strip() == 'force' 20 | reloaded = core.reload(force) 21 | if reloaded: 22 | await bot.send(event, '重新加载权限配置') 23 | else: 24 | await bot.send(event, '有未保存的修改,如放弃修改请添加force参数') 25 | 26 | 27 | @h(cg.command('save', permission=P('reload'))) 28 | async def _(bot: Bot, event: Event): 29 | success = core.save_all() 30 | if success: 31 | await bot.send(event, '已保存权限配置') 32 | else: 33 | await bot.send(event, '部分配置保存失败,请检查控制台输出') 34 | 35 | 36 | @h(cg.command('add', permission=P('edit.perm'), state={'add': True})) 37 | @h(cg.command('remove', permission=P('edit.perm'), state={'add': False})) 38 | async def _(bot: Bot, event: Event, state: T_State, 39 | raw_command: str = RawCommand(), arg: Message = CommandArg()): 40 | args = str(arg).split() 41 | 42 | # 一个参数,编辑当前会话的权限 43 | if len(args) == 1: 44 | item = args[0] 45 | designator = event 46 | # 两个参数,编辑指定权限组权限 47 | elif len(args) == 2: 48 | designator, item = args 49 | # 参数数量错误 50 | else: 51 | usage = f'用法:{raw_command} [[名称空间:]权限组名] 权限描述' 52 | return await bot.send(event, usage) 53 | 54 | # 阻止修饰 55 | if item.startswith('-'): 56 | item = '-/' + item[1:] 57 | else: 58 | item = '/' + item 59 | 60 | try: 61 | if state['add']: 62 | result = P.add_item(designator, item) 63 | else: 64 | result = P.remove_item(designator, item) 65 | except TypeError: 66 | await bot.send(event, '权限组不可修改') 67 | else: 68 | if result: 69 | await bot.send(event, '已修改权限组') 70 | else: 71 | await bot.send(event, '权限组中{}指定描述'.format('已有' if state['add'] else '没有')) 72 | 73 | 74 | @h(cg.command('addinh', permission=P('edit.inherit'), state={'add': True})) 75 | @h(cg.command('rminh', permission=P('edit.inherit'), state={'add': False})) 76 | async def _(bot: Bot, event: Event, state: T_State, 77 | raw_command: str = RawCommand(), arg: Message = CommandArg()): 78 | args = str(arg).split() 79 | 80 | # 一个参数,编辑当前会话的权限 81 | if len(args) == 1: 82 | target = args[0] 83 | designator = event 84 | # 两个参数,编辑指定权限组权限 85 | elif len(args) == 2: 86 | designator, target = args 87 | # 参数数量错误 88 | else: 89 | usage = f'用法:{raw_command} [[名称空间:]权限组名] [名称空间:]继承权限组名' 90 | return await bot.send(event, usage) 91 | 92 | try: 93 | if state['add']: 94 | result = P.add_inheritance(designator, target) 95 | else: 96 | result = P.remove_inheritance(designator, target) 97 | except KeyError: 98 | await bot.send(event, '权限组不存在') 99 | except TypeError: 100 | await bot.send(event, '权限组不可修改') 101 | else: 102 | if result: 103 | await bot.send(event, '已修改权限组') 104 | else: 105 | await bot.send(event, '权限组中{}指定继承关系'.format('已有' if state['add'] else '没有')) 106 | 107 | 108 | @h(cg.command('addgrp', permission=P('edit.group'), state={'add': True})) 109 | @h(cg.command('rmgrp', permission=P('edit.group'), state={'add': False, 'force': False})) 110 | @h(cg.command('rmgrpf', permission=P('edit.group.force'), state={'add': False, 'force': True})) 111 | async def _(bot: Bot, event: Event, state: T_State, 112 | raw_command: str = RawCommand(), arg: Message = CommandArg()): 113 | arg = str(arg).strip() 114 | 115 | # 无参数,编辑当前会话权限组 116 | if not arg: 117 | designator = event 118 | # 一个参数,编辑指定权限组 119 | elif not any(x.isspace() for x in arg): 120 | designator = arg 121 | # 参数数量错误 122 | else: 123 | usage = f'用法:{raw_command} [[名称空间:]权限组名]' 124 | return await bot.send(event, usage) 125 | 126 | try: 127 | if state['add']: 128 | P.add_group(designator) 129 | else: 130 | P.remove_group(designator, force=state['force']) 131 | except TypeError: 132 | await bot.send(event, '名称空间不可修改') 133 | except KeyError: 134 | await bot.send(event, '权限组{}存在'.format('已' if state['add'] else '不')) 135 | except ValueError: 136 | await bot.send(event, '权限组非空') 137 | else: 138 | await bot.send(event, '已{}权限组'.format('创建' if state['add'] else '删除')) 139 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/__init__.pyi: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Union, overload 3 | 4 | from nonebot.adapters import Bot, Event 5 | from nonebot.permission import Permission 6 | 7 | Designator = Union[Event, str, None] 8 | 9 | 10 | def register(plugin_name: str) -> "PluginHandler": 11 | """ 12 | 注册插件,并获取交互对象。 13 | 14 | :param plugin_name: 插件名称,不能为"global"。 15 | :return: 交互对象。 16 | """ 17 | 18 | class PluginHandler: 19 | def preset(self, preset: Path, decorate: bool = False) -> "PluginHandler": 20 | """ 21 | 设置预设权限组,会被加载到插件名对应的名称空间。 22 | 23 | :param preset: 包含权限组的文件路径。 24 | :param decorate: 是否自动修饰预设包含的权限名。 25 | :return: self 26 | """ 27 | 28 | def check_root(self) -> "PluginHandler": 29 | """ 30 | 设置自动检查根权限。 31 | 32 | :return: self 33 | """ 34 | 35 | def __call__(self, *perm: str, check_root: bool = ...) -> Permission: 36 | """ 37 | 创建权限检查器。若设置了 check_root ,则除了传入的权限外,还会检查本插件的根权限。 38 | 39 | 权限名会自动按下列规则修饰: 40 | 41 | - 空串,会修改为插件名,即根权限。 42 | - 以"/"开头的,去掉"/",不做其他修改。 43 | - 以"."开头的,在开头添加前一个权限名的修饰结果。若指定的第一个权限名就以"."开头,则添加插件名。 44 | - 否则,在开头添加 插件名+"." 。 45 | 46 | :param perm: 权限名,若传入多个权限则须同时满足。 47 | :param check_root: 如果传入布尔值,则替代之前 self.check_root() 的设定。 48 | :return: 权限检查器,可以直接传递给 nonebot 事件响应器。 49 | """ 50 | 51 | def has(self, *perm: str, bot: Bot = None, event: Event = None) -> bool: 52 | """ 53 | 检查事件是否具有指定权限。会修饰权限名,详见 __call__ 。不会自动检查根权限,无论是否设置 check_root 。 54 | 55 | :param perm: 权限名,若传入多个权限则须同时满足。 56 | :param bot: 机器人,默认为当前正在处理事件的机器人。 57 | :param event: 事件,默认为当前正在处理的事件。 58 | :return: 检查结果。 59 | """ 60 | 61 | @overload 62 | def add_permission(self, perm: str, *, 63 | comment: str = None, create_group: bool = True) -> bool: ... 64 | 65 | @overload 66 | def add_permission(self, designator: Designator, perm: str, *, 67 | comment: str = None, create_group: bool = True) -> bool: 68 | """ 69 | 向权限组添加一项权限。会修饰权限名。 70 | 71 | 实质是移除 perm 的"撤销"权限描述,并添加"授予"权限描述。 72 | 73 | :param designator: 权限组指示符。 74 | :param perm: 权限名。 75 | :param comment: 注释。 76 | :param create_group: 如果权限组不存在,是否自动创建。 77 | :return: 是否确实更改了,如果权限组中已有指定"授予"权限描述则返回 False 。 78 | :raise KeyError: 权限组不存在,并且指定为不自动创建。 79 | :raise TypeError: 权限组不可修改。 80 | """ 81 | 82 | @overload 83 | def remove_permission(self, perm: str, *, 84 | comment: str = None, create_group: bool = True) -> bool: ... 85 | 86 | @overload 87 | def remove_permission(self, designator: Designator, perm: str, *, 88 | comment: str = None, create_group: bool = True) -> bool: 89 | """ 90 | 从权限组去除一项权限。会修饰权限名。 91 | 92 | 实质是移除 perm 的"授予"权限描述,并添加"撤销"权限描述。 93 | 94 | :param designator: 权限组指示符。 95 | :param perm: 权限名。 96 | :param comment: 注释。 97 | :param create_group: 如果权限组不存在,是否自动创建。 98 | :return: 是否确实更改了,如果权限组中已有指定"撤销"权限描述则返回 False 。 99 | :raise KeyError: 权限组不存在,并且指定为不自动创建。 100 | :raise TypeError: 权限组不可修改。 101 | """ 102 | 103 | @overload 104 | def reset_permission(self, perm: str, *, allow_missing: bool = True) -> bool: ... 105 | 106 | @overload 107 | def reset_permission(self, designator: Designator, perm: str, *, allow_missing: bool = True) -> bool: 108 | """ 109 | 把权限组中关于一项权限的描述恢复默认。会修饰权限名。 110 | 111 | 实质是移除 perm 的"授予"和"撤销"权限描述,使得检查时会检索到更低层级权限组的设置。 112 | 113 | :param designator: 权限组指示符。 114 | :param perm: 权限名。 115 | :param allow_missing: 如果权限组不存在,是否静默忽略。 116 | :return: 是否确实更改了,如果权限组中没有指定"授予"和"撤销"权限描述则返回 False 。 117 | :raise KeyError: 权限组不存在,并且指定为不静默忽略。 118 | :raise TypeError: 权限组不可修改。 119 | """ 120 | 121 | @overload 122 | def add_item(self, item: str, *, 123 | comment: str = None, create_group: bool = True) -> bool: ... 124 | 125 | @overload 126 | def add_item(self, designator: Designator, item: str, *, 127 | comment: str = None, create_group: bool = True) -> bool: 128 | """ 129 | 向权限组添加权限描述。会修饰权限名。 130 | 131 | :param designator: 权限组指示符。 132 | :param item: 权限描述。 133 | :param comment: 注释。 134 | :param create_group: 如果权限组不存在,是否自动创建。 135 | :return: 是否确实添加了,如果权限组中已有指定描述则返回 False 。 136 | :raise KeyError: 权限组不存在,并且指定为不自动创建。 137 | :raise TypeError: 权限组不可修改。 138 | """ 139 | 140 | @overload 141 | def remove_item(self, item: str, *, allow_missing: bool = True) -> bool: ... 142 | 143 | @overload 144 | def remove_item(self, designator: Designator, item: str, *, allow_missing: bool = True) -> bool: 145 | """ 146 | 从权限组中移除权限描述。会修饰权限名。 147 | 148 | :param designator: 权限组指示符。 149 | :param item: 权限描述。 150 | :param allow_missing: 如果权限组不存在,是否静默忽略。 151 | :return: 是否确实移除了,如果权限组中没有指定描述则返回 False 。 152 | :raise KeyError: 权限组不存在,并且指定为不静默忽略。 153 | :raise TypeError: 权限组不可修改。 154 | """ 155 | 156 | @overload 157 | def add_inheritance(self, target: Designator, *, 158 | comment: str = None, create_group: bool = True) -> bool: ... 159 | 160 | @overload 161 | def add_inheritance(self, designator: Designator, target: Designator, *, 162 | comment: str = None, create_group: bool = True) -> bool: 163 | """ 164 | 向权限组添加继承关系。 165 | 166 | :param designator: 待修改权限组的指示符。 167 | :param target: 需继承权限组的指示符。如省略名称空间则默认为当前插件。 168 | :param comment: 注释。 169 | :param create_group: 如果待修改权限组不存在,是否自动创建。 170 | :return: 是否确实添加了,如果权限组中已有指定继承关系则返回 False 。 171 | :raise KeyError: 待修改权限组不存在,并且指定为不自动创建;或需继承的权限组不存在。 172 | :raise TypeError: 权限组不可修改。 173 | """ 174 | 175 | @overload 176 | def remove_inheritance(self, target: Designator, *, 177 | allow_missing: bool = True) -> bool: ... 178 | 179 | @overload 180 | def remove_inheritance(self, designator: Designator, target: Designator, *, 181 | allow_missing: bool = True) -> bool: 182 | """ 183 | 从权限组中移除继承关系。 184 | 185 | :param designator: 待修改权限组的指示符。 186 | :param target: 需移除继承权限组的指示符。如省略名称空间则默认为当前插件。 187 | :param allow_missing: 如果待修改权限组不存在,是否静默忽略。 188 | :return: 是否确实移除了,如果权限组中没有指定继承关系则返回 False 。 189 | :raise KeyError: 待修改权限组不存在,并且指定为不静默忽略;或需移除继承的权限组不存在。 190 | :raise TypeError: 权限组不可修改。 191 | """ 192 | 193 | def add_group(self, designator: Designator = None, *, comment: str = None) -> None: 194 | """ 195 | 创建权限组。 196 | 197 | :param designator: 权限组指示符。 198 | :param comment: 注释。 199 | :raise KeyError: 权限组已存在。 200 | :raise TypeError: 名称空间不可修改。 201 | """ 202 | 203 | def remove_group(self, designator: Designator = None, *, force: bool = False) -> None: 204 | """ 205 | 移除权限组。 206 | 207 | :param designator: 权限组指示符。 208 | :param force: 是否允许移除非空的权限组。 209 | :raise KeyError: 权限组不存在。 210 | :raise ValueError: 因权限组非空而没有移除。 211 | :raise TypeError: 名称空间不可修改。 212 | """ 213 | -------------------------------------------------------------------------------- /docs/interface.md: -------------------------------------------------------------------------------- 1 | # 接口文档 2 | 3 | ## register 4 | 5 | 注册一个需要使用本插件功能的插件,并获取交互对象。 6 | 7 | 参数: 8 | 9 | - `plugin_name: str`,插件名。为避免冲突,建议使用插件包名(去掉`nonebot_plugin_`前缀)。 10 | 11 | 💬 插件名不能为 "global" ,强烈不建议使用 "user" 或 "group" 。 12 | 13 | 返回类型:`PluginHandler` 14 | 15 | 示例: 16 | 17 | ```python 18 | from nonebot import require 19 | require("nonebot_plugin_flexperm") 20 | from nonebot_plugin_flexperm import register 21 | P = register("my_plugin") 22 | ``` 23 | 24 | 注意:`import`之前调用`require`是必须的。为了避免不同来源分别加载本插件导致配置文件管理混乱,本插件被设计为**不允许通过`import`加载**。 25 | 26 | 扩展阅读:[权限名称修饰](#权限名称修饰)。 27 | 28 | ### 类型标注 29 | 30 | 本插件在`__init__.pyi`文件中提供了`PluginHandler`类的接口说明,可用于支持 IDE 的代码提示、自动补全等功能。 31 | 32 | `register`函数已添加相应的类型标注,一般来说IDE可以自动识别。如果不行,也可以通过下面的方式手动标注: 33 | 34 | ```python 35 | from nonebot_plugin_flexperm import PluginHandler 36 | P: PluginHandler = register("my_plugin") 37 | ``` 38 | 39 | ## PluginHandler 40 | 41 | 通过`register`获得的交互对象。 42 | 43 | ### \_\_call__ 44 | 45 | 创建权限检查器。 46 | 47 | 参数: 48 | 49 | - `*perm: str`,需要检查的权限,若传入多个则须全部满足。 50 | - 以下参数只能以关键字参数形式传入。 51 | - `check_root: bool = ...`,如果传入布尔值,则替代之前`self.check_root()`的设定。 52 | 53 | 返回类型:`Permission` 54 | 55 | 示例: 56 | 57 | ```python 58 | from nonebot import on_command 59 | cmd = on_command("my_command", permission=P("my_command")) 60 | ``` 61 | 62 | ### has 63 | 64 | 检查事件是否有指定权限,类似于未封装为检查器版本的`__call__`。 65 | 66 | 参数: 67 | 68 | - `*perm: str`,需要检查的权限,若传入多个则须全部满足。 69 | - `bot: Bot = None`,机器人,默认为当前正在处理事件的机器人。 70 | - `event: Event = None`,事件,默认为当前正在处理的事件。 71 | 72 | 返回类型:`bool` 73 | 74 | 示例: 75 | 76 | ```python 77 | @cmd.handle() 78 | async def _(bot, event): 79 | if P.has("my_command.inner"): 80 | ... 81 | ``` 82 | 83 | ### preset 84 | 85 | 设置插件预设权限配置。多次设置仅最后一次有效。 86 | 87 | 参数: 88 | 89 | - `preset: Path`,预设文件路径。 90 | - `decorate: bool = False`,是否自动[修饰](#权限名称修饰)预设包含的权限名。 91 | 92 | 返回`self`,可以在`register`之后直接链式调用。 93 | 94 | 示例: 95 | 96 | ```python 97 | from pathlib import Path 98 | 99 | P.preset(Path(__file__).parent / "preset.yml") 100 | ``` 101 | 102 | 说明: 103 | 104 | 预设配置会被加载到与插件同名的名称空间中,允许被其他配置文件引用。与[六个默认权限组](permdesc.md#默认权限组)同名的会被相应组自动继承,无论后者保持默认还是使用自定义设置。 105 | 106 | 例如,上述示例代码的同一目录下`preset.yml`文件内容为: 107 | 108 | ```yaml 109 | superuser: 110 | permissions: 111 | - my_plugin.* 112 | 113 | editor: 114 | permissions: 115 | - my_plugin.read 116 | - my_plugin.write 117 | ``` 118 | 119 | 此时,权限组`global:superuser`将自动继承`my_plugin:superuser`,因此超级用户自动具有`my_plugin`下所有子权限(除非被撤销)。权限组`my_plugin:editor`不会直接起作用,但其他权限组可以通过继承它来获得`my_plugin.read`和`my_plugin.write`两项权限。 120 | 121 | 如果设置了`decorate`参数,预设配置涉及到的权限名称会被自动[修饰](#权限名称修饰),此时就不必在每个描述前手动添加插件名。例如,下面这种写法与上一个例子等效: 122 | 123 | ```python 124 | P.preset(Path(__file__).parent / "preset.yml", decorate=True) 125 | ``` 126 | 127 | ```yaml 128 | superuser: 129 | permissions: 130 | - "*" 131 | 132 | editor: 133 | permissions: 134 | - read 135 | - write 136 | ``` 137 | 138 | ### check_root 139 | 140 | 设置`__call__`自动检查根权限。 141 | 142 | 参数:无 143 | 144 | 返回`self`,可以在`register`之后直接链式调用。 145 | 146 | 示例: 147 | 148 | ```python 149 | P.check_root() 150 | ``` 151 | 152 | 说明: 153 | 154 | 未设置`check_root`的情况下,`__call__`返回的检查器只会检查显式传递给它的权限,例如`P("a.b.c")`会检查`my_plugin.a.b.c`权限。设置`check_root`后,`P("a.b.c")`将检查`my_plugin`和`my_plugin.a.b.c`两个权限,用户必须同时拥有这两个权限才能使用功能。主要可以应用于在不同会话中启用/禁用整个插件。 155 | 156 | 只作用于`__call__`,`has`不受影响。 157 | 158 | ### add_permission 159 | 160 | 向权限组添加一项权限。 161 | 162 | 实质是移除相应"撤销"权限描述,并添加"授予"权限描述,因此存在被更高优先级的权限组覆盖的可能。 163 | 164 | 参数: 165 | 166 | - `designator: Union[Event, str, None]`,[权限组指示符](#权限组指示符),可以缺省为`None`。 167 | - `perm: str`,权限名。 168 | - 以下参数只能以关键字参数形式传入。 169 | - `comment: str = None`,注释,会以 YAML 注释的形式添加在配置文件对应项目的行尾。 170 | - `create_group: bool = True`,如果权限组不存在,是否自动创建。 171 | 172 | 返回: 173 | 174 | `bool`,是否确实更改了,如果权限组中已有指定"授予"权限描述则返回`False`。 175 | 176 | 可能抛出的异常及原因: 177 | 178 | - `KeyError`: 权限组不存在,并且指定为不自动创建。 179 | - `TypeError`: 权限组不可修改。 180 | 181 | ### remove_permission 182 | 183 | 从权限组去除一项权限。 184 | 185 | 实质是移除相应"授予"权限描述,并添加"撤销"权限描述,因此存在被更高优先级的权限组覆盖的可能。 186 | 187 | 参数: 188 | 189 | - `designator: Union[Event, str, None]`,[权限组指示符](#权限组指示符),可以缺省为`None`。 190 | - `perm: str`,权限名。 191 | - 以下参数只能以关键字参数形式传入。 192 | - `comment: str = None`,注释,会以 YAML 注释的形式添加在配置文件对应项目的行尾。 193 | - `create_group: bool = True`,如果权限组不存在,是否自动创建。 194 | 195 | 返回: 196 | 197 | `bool`,是否确实更改了,如果权限组中已有指定"撤销"权限描述则返回`False`。 198 | 199 | 可能抛出的异常及原因: 200 | 201 | - `KeyError`: 权限组不存在,并且指定为不自动创建。 202 | - `TypeError`: 权限组不可修改。 203 | 204 | ### reset_permission 205 | 206 | 把权限组中关于一项权限的描述恢复默认。 207 | 208 | 实质是移除相应的"授予"和"撤销"权限描述,因此存在被更高优先级的权限组覆盖的可能。 209 | 210 | 参数: 211 | 212 | - `designator: Union[Event, str, None]`,[权限组指示符](#权限组指示符),可以缺省为`None`。 213 | - `perm: str`,权限名。 214 | - 以下参数只能以关键字参数形式传入。 215 | - `allow_missing: bool = True`,如果权限组不存在,是否静默忽略。 216 | 217 | 返回: 218 | 219 | `bool`,是否确实更改了,如果权限组中没有指定"授予"和"撤销"权限描述则返回`False`。 220 | 221 | 可能抛出的异常及原因: 222 | 223 | - `KeyError`: 权限组不存在,并且指定为不静默忽略。 224 | - `TypeError`: 权限组不可修改。 225 | 226 | ### add_item 227 | 228 | 向权限组添加权限描述。 229 | 230 | 参数: 231 | 232 | - `designator: Union[Event, str, None]`,[权限组指示符](#权限组指示符),可以缺省为`None`。 233 | - `item: str`,权限描述。 234 | - 以下参数只能以关键字参数形式传入。 235 | - `comment: str = None`,注释,会以 YAML 注释的形式添加在配置文件对应项目的行尾。 236 | - `create_group: bool = True`,如果权限组不存在,是否自动创建。 237 | 238 | 返回: 239 | 240 | `bool`,是否确实添加了,如果权限组中已有指定描述则返回`False`。 241 | 242 | 可能抛出的异常及原因: 243 | 244 | - `KeyError`: 权限组不存在,并且指定为不自动创建。 245 | - `TypeError`: 权限组不可修改。 246 | 247 | ### remove_item 248 | 249 | 从权限组中移除权限描述。 250 | 251 | 参数: 252 | 253 | - `designator: Union[Event, str, None]`,[权限组指示符](#权限组指示符),可以缺省为`None`。 254 | - `item: str`,权限描述。 255 | - 以下参数只能以关键字参数形式传入。 256 | - `allow_missing: bool = True`,如果权限组不存在,是否静默忽略。 257 | 258 | 返回: 259 | 260 | `bool`,是否确实移除了,如果权限组中没有指定描述则返回`False`。 261 | 262 | 可能抛出的异常及原因: 263 | 264 | - `KeyError`: 权限组不存在,并且指定为不静默忽略。 265 | - `TypeError`: 权限组不可修改。 266 | 267 | ### add_inheritance 268 | 269 | 向权限组添加继承关系。 270 | 271 | 参数: 272 | 273 | - `designator: Union[Event, str, None]`,待修改权限组的[指示符](#权限组指示符),可以缺省为`None`。 274 | - `target: Union[Event, str, None]`,需继承权限组的[指示符](#权限组指示符)。**如省略名称空间则默认为当前插件。** 275 | - 以下参数只能以关键字参数形式传入。 276 | - `comment: str = None`,注释,会以 YAML 注释的形式添加在配置文件对应项目的行尾。 277 | - `create_group: bool = True`,如果待修改权限组不存在,是否自动创建。 278 | 279 | 返回: 280 | 281 | `bool`,是否确实添加了,如果权限组中已有指定继承关系则返回`False`。 282 | 283 | 可能抛出的异常及原因: 284 | 285 | - `KeyError`: 待修改权限组不存在,并且指定为不自动创建;或需继承的权限组不存在。 286 | - `TypeError`: 权限组不可修改。 287 | 288 | ### remove_inheritance 289 | 290 | 从权限组中移除继承关系。 291 | 292 | 参数: 293 | 294 | - `designator: Union[Event, str, None]`,待修改权限组的[指示符](#权限组指示符),可以缺省为`None`。 295 | - `target: Union[Event, str, None]`,需移除继承权限组的[指示符](#权限组指示符)。**如省略名称空间则默认为当前插件。** 296 | - 以下参数只能以关键字参数形式传入。 297 | - `allow_missing: bool = True`,如果待修改权限组不存在,是否静默忽略。 298 | 299 | 返回: 300 | 301 | `bool`,是否确实移除了,如果权限组中没有指定继承关系则返回`False`。 302 | 303 | 可能抛出的异常及原因: 304 | 305 | - `KeyError`: 待修改权限组不存在,并且指定为不静默忽略;或需移除继承的权限组不存在。 306 | - `TypeError`: 权限组不可修改。 307 | 308 | ### add_group 309 | 310 | 创建权限组。 311 | 312 | 参数: 313 | 314 | - `designator: Union[Event, str, None] = None`,[权限组指示符](#权限组指示符)。 315 | - 以下参数只能以关键字参数形式传入。 316 | - `comment: str = None`,注释,会以 YAML 注释的形式添加在配置文件对应项目的行尾。 317 | 318 | 可能抛出的异常及原因: 319 | 320 | - `KeyError`: 权限组已存在。 321 | - `TypeError`: 名称空间不可修改。 322 | 323 | ### remove_group 324 | 325 | 创建权限组。 326 | 327 | 参数: 328 | 329 | - `designator: Union[Event, str, None] = None`,[权限组指示符](#权限组指示符)。 330 | - 以下参数只能以关键字参数形式传入。 331 | - `force: bool = False`,是否允许移除非空的权限组。 332 | 333 | 可能抛出的异常及原因: 334 | 335 | - `KeyError`: 权限组不存在。 336 | - `ValueError`: 因权限组非空而没有移除。 337 | - `TypeError`: 名称空间不可修改。 338 | 339 | # 词条解释 340 | 341 | ## 权限组指示符 342 | 343 | 权限组指示符可以是一个字符串、一个 NoneBot `Event` 对象或者`None`,在本插件的接口中的出现形式通常是名为`designator`的参数。指示符会按下列规则解释为名称空间和权限组名: 344 | 345 | - 如果指示符是字符串: 346 | - 如果指示符包含冒号,则以第一个冒号之前的内容为名称空间,之后的内容为权限组名。 347 | - 如果名称空间是`group`或`user`,并且权限组名是一个整数,则自动调整为整型,否则保持为字符串。 348 | - 如果指示符不包含冒号,则**一般**代表`global`名称空间,整个指示符为权限组名。 349 | - 少数接口参数会将其解释为当前插件名称空间([1](#add_inheritance) [2](#remove_inheritance)这两个接口的`target`参数)。 350 | - 如果指示符是`Event`对象: 351 | - 对于群聊消息事件,代表`group`名称空间,群号为权限组名。 352 | - 对于私聊消息事件,代表`user`名称空间,用户ID(QQ号)为权限组名。 353 | - 其他事件类型暂不支持。 354 | - 如果指示符是`None`: 355 | - 视为当前正在处理的事件。 356 | 357 | ## 权限名称修饰 358 | 359 | 通过 `register` 注册获得的 `PluginHandler` 对象保存了注册时传递的插件名,若无特殊说明,调用该对象接口时传递的权限名称(包括权限描述的权限名称部分)均会被修饰。 360 | 361 | 💬 修饰功能主要是考虑到不同插件作者可能选用相同的权限名称,用于避免不同插件的权限互相干扰。当前通过交互对象提供的接口全部有修饰处理,所以通常的应用无需特别考虑,只要编辑和检查权限时使用一致的写法即可指代同一权限。需要用户明确感知到修饰功能的场景主要有:(1) 直接编辑权限配置文件,(2) 使用本插件自己的[命令](command.md)。 362 | 363 | 修饰规则为: 364 | 365 | - 空串,改为插件名,即根权限。 366 | - 以`/`开头的,去掉`/`,不做其他修改。 367 | - 以`.`开头的,在开头添加前一个权限名的修饰结果。若指定的第一个权限名就以`.`开头,则添加插件名。 368 | - 否则,在开头添加 插件名+`.` 。 369 | 370 | 例如: 371 | 372 | - `P("")`检查`my_plugin`权限 373 | - `P("a.b")`检查`my_plugin.a.b`权限 374 | - `P("/a.b")`检查`a.b`权限 375 | - `P("/a", ".b", ".c")`检查`a`、`a.b`和`a.b.c`三个权限 376 | - `P("a", ".b", "/c")`检查`my_plugin.a`、`my_plugin.a.b`和`c`三个权限 377 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/plugin.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from pathlib import Path 3 | from typing import Optional, Dict, Union, Tuple 4 | 5 | from nonebot.adapters import Bot, Event 6 | from nonebot.log import logger 7 | from nonebot.matcher import current_bot, current_event 8 | from nonebot.permission import Permission 9 | 10 | from .check import check, get_permission_group_by_event 11 | from .core import get, get_namespace, PermissionGroup, decorate_permission, parse_qualified_group_name 12 | 13 | plugins: Dict[str, "PluginHandler"] = {} 14 | 15 | _sentinel = object() 16 | Designator = Union[Event, str, None] 17 | 18 | 19 | def register(plugin_name: str) -> "PluginHandler": 20 | """ 21 | 注册插件,并获取交互对象。 22 | 23 | :param plugin_name: 插件名称,不能为"global"。 24 | :return: 交互对象。 25 | """ 26 | if plugin_name == 'global': 27 | raise ValueError('Plugin shall not be named "global".') 28 | if plugin_name in plugins: 29 | logger.warning(f'Plugin {plugin_name} is registered twice!') 30 | return plugins[plugin_name] 31 | handler = PluginHandler(plugin_name) 32 | plugins[plugin_name] = handler 33 | return handler 34 | 35 | 36 | class PluginHandler: 37 | def __init__(self, name: str): 38 | self.name = name 39 | self.preset_: Optional[Path] = None 40 | self.decorate_ = False 41 | self.check_root_ = False 42 | 43 | def preset(self, preset: Path, decorate: bool = False): 44 | """ 45 | 设置预设权限组,会被加载到插件名对应的名称空间。 46 | 47 | :param preset: 包含权限组的文件路径。 48 | :param decorate: 是否自动修饰预设包含的权限名。 49 | :return: self 50 | """ 51 | self.preset_ = preset 52 | self.decorate_ = decorate 53 | return self 54 | 55 | def check_root(self): 56 | """ 57 | 设置自动检查根权限。 58 | 59 | :return: self 60 | """ 61 | self.check_root_ = True 62 | return self 63 | 64 | def __call__(self, *perm: str, check_root: bool = _sentinel) -> Permission: 65 | """ 66 | 创建权限检查器。若设置了 check_root ,则除了传入的权限外,还会检查本插件的根权限。 67 | 68 | 权限名会自动按下列规则修饰: 69 | 70 | - 空串,会修改为插件名,即根权限。 71 | - 以"/"开头的,去掉"/",不做其他修改。 72 | - 以"."开头的,在开头添加前一个权限名的修饰结果。若指定的第一个权限名就以"."开头,则添加插件名。 73 | - 否则,在开头添加 插件名+"." 。 74 | 75 | :param perm: 权限名,若传入多个权限则须同时满足。 76 | :param check_root: 如果传入布尔值,则替代之前 self.check_root() 的设定。 77 | :return: 权限检查器,可以直接传递给 nonebot 事件响应器。 78 | """ 79 | full = decorate_permission(self.name, perm) 80 | if check_root is _sentinel: 81 | check_root = self.check_root_ 82 | if check_root: 83 | full.insert(0, self.name) 84 | 85 | if len(full) == 1: 86 | single = full[0] 87 | 88 | async def _check(bot: Bot, event: Event): 89 | return check(bot, event, single) 90 | else: 91 | async def _check(bot: Bot, event: Event): 92 | return all(check(bot, event, px) for px in full) 93 | 94 | return Permission(_check) 95 | 96 | def has(self, *perm: str, bot: Bot = None, event: Event = None) -> bool: 97 | """ 98 | 检查事件是否具有指定权限。会修饰权限名,详见 __call__ 。不会自动检查根权限,无论是否设置 check_root 。 99 | 100 | :param perm: 权限名,若传入多个权限则须同时满足。 101 | :param bot: 机器人,默认为当前正在处理事件的机器人。 102 | :param event: 事件,默认为当前正在处理的事件。 103 | :return: 检查结果。 104 | """ 105 | if bot is None or event is None: 106 | bot = current_bot.get() 107 | if event is None: 108 | event = current_event.get() 109 | full = decorate_permission(self.name, perm) 110 | return all(check(bot, event, px) for px in full) 111 | 112 | def add_permission(self, designator: Designator, perm: str = _sentinel, *, 113 | comment: str = None, create_group: bool = True) -> bool: 114 | """ 115 | 向权限组添加一项权限。会修饰权限名。 116 | 117 | 实质是移除 perm 的"撤销"权限描述,并添加"授予"权限描述。 118 | 119 | :param designator: 权限组指示符。 120 | :param perm: 权限名。 121 | :param comment: 注释。 122 | :param create_group: 如果权限组不存在,是否自动创建。 123 | :return: 是否确实更改了,如果权限组中已有指定"授予"权限描述则返回 False 。 124 | :raise KeyError: 权限组不存在,并且指定为不自动创建。 125 | :raise TypeError: 权限组不可修改。 126 | """ 127 | if perm is _sentinel: 128 | designator, perm = None, designator 129 | 130 | group_ = self._get_or_create_group(designator, create_group, True) 131 | [perm] = decorate_permission(self.name, [perm]) 132 | with contextlib.suppress(ValueError): 133 | group_.remove('-' + perm) 134 | try: 135 | group_.add(perm, comment) 136 | return True 137 | except ValueError: 138 | return False 139 | 140 | def remove_permission(self, designator: Designator, perm: str = _sentinel, *, 141 | comment: str = None, create_group: bool = True) -> bool: 142 | """ 143 | 从权限组去除一项权限。会修饰权限名。 144 | 145 | 实质是移除 perm 的"授予"权限描述,并添加"撤销"权限描述。 146 | 147 | :param designator: 权限组指示符。 148 | :param perm: 权限名。 149 | :param comment: 注释。 150 | :param create_group: 如果权限组不存在,是否自动创建。 151 | :return: 是否确实更改了,如果权限组中已有指定"撤销"权限描述则返回 False 。 152 | :raise KeyError: 权限组不存在,并且指定为不自动创建。 153 | :raise TypeError: 权限组不可修改。 154 | """ 155 | if perm is _sentinel: 156 | designator, perm = None, designator 157 | 158 | group_ = self._get_or_create_group(designator, create_group, True) 159 | [perm] = decorate_permission(self.name, [perm]) 160 | with contextlib.suppress(ValueError): 161 | group_.remove(perm) 162 | try: 163 | group_.add('-' + perm, comment) 164 | return True 165 | except ValueError: 166 | return False 167 | 168 | def reset_permission(self, designator: Designator, perm: str = _sentinel, *, 169 | allow_missing: bool = True) -> bool: 170 | """ 171 | 把权限组中关于一项权限的描述恢复默认。会修饰权限名。 172 | 173 | 实质是移除 perm 的"授予"和"撤销"权限描述,使得检查时会检索到更低层级权限组的设置。 174 | 175 | :param designator: 权限组指示符。 176 | :param perm: 权限名。 177 | :param allow_missing: 如果权限组不存在,是否静默忽略。 178 | :return: 是否确实更改了,如果权限组中没有指定"授予"和"撤销"权限描述则返回 False 。 179 | :raise KeyError: 权限组不存在,并且指定为不静默忽略。 180 | :raise TypeError: 权限组不可修改。 181 | """ 182 | if perm is _sentinel: 183 | designator, perm = None, designator 184 | 185 | group_ = self._get_or_create_group(designator, allow_missing, False) 186 | if group_ is None: 187 | return False 188 | [perm] = decorate_permission(self.name, [perm]) 189 | # noinspection PyUnusedLocal 190 | modified = False 191 | with contextlib.suppress(ValueError): 192 | group_.remove(perm) 193 | # noinspection PyUnusedLocal 194 | modified = True 195 | with contextlib.suppress(ValueError): 196 | group_.remove('-' + perm) 197 | modified = True 198 | return modified 199 | 200 | def add_item(self, designator: Designator, item: str = _sentinel, *, 201 | comment: str = None, create_group: bool = True) -> bool: 202 | """ 203 | 向权限组添加权限描述。会修饰权限名。 204 | 205 | :param designator: 权限组指示符。 206 | :param item: 权限描述。 207 | :param comment: 注释。 208 | :param create_group: 如果权限组不存在,是否自动创建。 209 | :return: 是否确实添加了,如果权限组中已有指定描述则返回 False 。 210 | :raise KeyError: 权限组不存在,并且指定为不自动创建。 211 | :raise TypeError: 权限组不可修改。 212 | """ 213 | if item is _sentinel: 214 | designator, item = None, designator 215 | 216 | group_ = self._get_or_create_group(designator, create_group, True) 217 | if item.startswith('-'): 218 | [item] = decorate_permission(self.name, [item[1:]]) 219 | item = '-' + item 220 | else: 221 | [item] = decorate_permission(self.name, [item]) 222 | try: 223 | group_.add(item, comment) 224 | return True 225 | except ValueError: 226 | return False 227 | 228 | def remove_item(self, designator: Designator, item: str = _sentinel, *, 229 | allow_missing: bool = True) -> bool: 230 | """ 231 | 从权限组中移除权限描述。会修饰权限名。 232 | 233 | :param designator: 权限组指示符。 234 | :param item: 权限描述。 235 | :param allow_missing: 如果权限组不存在,是否静默忽略。 236 | :return: 是否确实移除了,如果权限组中没有指定描述则返回 False 。 237 | :raise KeyError: 权限组不存在,并且指定为不静默忽略。 238 | :raise TypeError: 权限组不可修改。 239 | """ 240 | if item is _sentinel: 241 | designator, item = None, designator 242 | 243 | group_ = self._get_or_create_group(designator, allow_missing, False) 244 | if group_ is None: 245 | return False 246 | if item.startswith('-'): 247 | [item] = decorate_permission(self.name, [item[1:]]) 248 | item = '-' + item 249 | else: 250 | [item] = decorate_permission(self.name, [item]) 251 | try: 252 | group_.remove(item) 253 | return True 254 | except ValueError: 255 | return False 256 | 257 | def add_inheritance(self, designator: Designator, target: Designator = _sentinel, *, 258 | comment: str = None, create_group: bool = True) -> bool: 259 | """ 260 | 向权限组添加继承关系。 261 | 262 | :param designator: 待修改权限组的指示符。 263 | :param target: 需继承权限组的指示符。如省略名称空间则默认为当前插件。 264 | :param comment: 注释。 265 | :param create_group: 如果待修改权限组不存在,是否自动创建。 266 | :return: 是否确实添加了,如果权限组中已有指定继承关系则返回 False 。 267 | :raise KeyError: 待修改权限组不存在,并且指定为不自动创建;或需继承的权限组不存在。 268 | :raise TypeError: 权限组不可修改。 269 | """ 270 | if target is _sentinel: 271 | designator, target = None, designator 272 | 273 | ancestor = self._get_or_create_group(target, False, False, self.name) 274 | group_ = self._get_or_create_group(designator, create_group, True) 275 | try: 276 | group_.add_inheritance(ancestor, comment) 277 | return True 278 | except ValueError: 279 | return False 280 | 281 | def remove_inheritance(self, designator: Designator, target: Designator = _sentinel, *, 282 | allow_missing: bool = True) -> bool: 283 | """ 284 | 从权限组中移除继承关系。 285 | 286 | :param designator: 待修改权限组的指示符。 287 | :param target: 需移除继承权限组的指示符。如省略名称空间则默认为当前插件。 288 | :param allow_missing: 如果待修改权限组不存在,是否静默忽略。 289 | :return: 是否确实移除了,如果权限组中没有指定继承关系则返回 False 。 290 | :raise KeyError: 待修改权限组不存在,并且指定为不静默忽略;或需移除继承的权限组不存在。 291 | :raise TypeError: 权限组不可修改。 292 | """ 293 | if target is _sentinel: 294 | designator, target = None, designator 295 | 296 | group_ = self._get_or_create_group(designator, allow_missing, False) 297 | if group_ is None: 298 | return False 299 | ancestor = self._get_or_create_group(target, False, False, self.name) 300 | try: 301 | group_.remove_inheritance(ancestor) 302 | return True 303 | except ValueError: 304 | return False 305 | 306 | @classmethod 307 | def add_group(cls, designator: Designator = None, *, comment: str = None): 308 | """ 309 | 创建权限组。 310 | 311 | :param designator: 权限组指示符。 312 | :param comment: 注释。 313 | :raise KeyError: 权限组已存在。 314 | :raise TypeError: 名称空间不可修改。 315 | """ 316 | namespace, group = cls._parse_designator(designator) 317 | get_namespace(namespace, False).add_group(group, comment) 318 | 319 | @classmethod 320 | def remove_group(cls, designator: Designator = None, *, force: bool = False): 321 | """ 322 | 移除权限组。 323 | 324 | :param designator: 权限组指示符。 325 | :param force: 是否允许移除非空的权限组。 326 | :raise KeyError: 权限组不存在。 327 | :raise ValueError: 因权限组非空而没有移除。 328 | :raise TypeError: 名称空间不可修改。 329 | """ 330 | namespace, group = cls._parse_designator(designator) 331 | get_namespace(namespace, False).remove_group(group, force) 332 | 333 | @classmethod 334 | def _parse_designator(cls, designator: Designator, default_namespace: str = 'global' 335 | ) -> Tuple[str, Union[str, int]]: 336 | if designator is None: 337 | designator = current_event.get() 338 | if isinstance(designator, Event): 339 | bot = current_bot.get() 340 | result = get_permission_group_by_event(bot, designator) 341 | if result is not None: 342 | return result 343 | raise ValueError('Unrecognized event type: ' + designator.get_event_name()) 344 | if isinstance(designator, str): 345 | return parse_qualified_group_name(designator, default_namespace) 346 | raise ValueError(f'Invalid designator: {type(designator)}') 347 | 348 | @classmethod 349 | def _get_or_create_group(cls, designator: Designator, silent: bool, create: bool, default_namespace: str = 'global' 350 | ) -> Optional[PermissionGroup]: 351 | namespace, group_name = cls._parse_designator(designator, default_namespace) 352 | group = get(namespace, group_name) 353 | if not group.is_valid: 354 | if not silent: 355 | raise KeyError('No such group') 356 | if not create: 357 | return None 358 | get_namespace(namespace, False).add_group(group_name) 359 | group = get(namespace, group_name) 360 | return group 361 | -------------------------------------------------------------------------------- /nonebot_plugin_flexperm/core.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | from collections import OrderedDict 3 | from contextlib import contextmanager 4 | from enum import Enum 5 | from pathlib import Path 6 | from typing import TYPE_CHECKING, Optional, Union, Tuple, List, Set, Dict, Iterable 7 | 8 | import nonebot 9 | from nonebot.log import logger 10 | from pydantic import BaseModel, parse_obj_as 11 | from ruamel.yaml import YAML, YAMLError, CommentedMap, CommentedSeq 12 | 13 | from .config import c 14 | from .util import try_int 15 | 16 | nonebot.require('nonebot_plugin_apscheduler') 17 | from nonebot_plugin_apscheduler import scheduler 18 | 19 | yaml = YAML() 20 | nonebot_driver = nonebot.get_driver() 21 | 22 | loaded: Dict[str, "Namespace"] = {} 23 | loaded_by_path: Dict[Path, "Namespace"] = {} 24 | plugin_namespaces: List["Namespace"] = [] 25 | default_groups: Set[str] = set() 26 | 27 | 28 | def get(namespace: str, group: Union[str, int], referer: "PermissionGroup" = None, required: bool = False 29 | ) -> "PermissionGroup": 30 | """ 31 | 获取权限组。 32 | 33 | :param namespace: 名称空间。 34 | :param group: 组名。 35 | :param referer: 引用者。 36 | :param required: 权限组不存在时是否报错。若存在但有其他问题,则无论该参数设置,都会报错。 37 | :return: 权限组,若失败则返回一个空组。 38 | """ 39 | return get_namespace(namespace, required).get_group(group, referer, required) 40 | 41 | 42 | def get_namespace(namespace: str, required: bool, path_override: Path = None) -> "Namespace": 43 | ns = loaded.get(namespace) 44 | if ns is None: 45 | path = path_override or c.flexperm_base / f'{namespace}.yml' 46 | path = path.resolve() 47 | ns = loaded_by_path.get(path) 48 | if ns is None: 49 | ns = Namespace(namespace, path, required=required, modifiable=path_override is None) 50 | loaded_by_path[path] = ns 51 | loaded[namespace] = ns 52 | return ns 53 | 54 | 55 | @nonebot_driver.on_startup 56 | def reload(force: bool = False) -> bool: 57 | """ 58 | 使所有权限组在下一次使用时重新从配置加载。 59 | 60 | :param force: 强制重新加载,忽略未保存的修改。 61 | :return: 因有未保存的修改而没有重新加载时返回 False ,否则返回 True 。 62 | """ 63 | if not force and any(x.dirty for x in loaded.values()): 64 | return False 65 | 66 | loaded.clear() 67 | loaded_by_path.clear() 68 | plugin_namespaces.clear() 69 | default_groups.clear() 70 | 71 | # 默认权限组 72 | global_ = get_namespace('global', False) 73 | defaults = Namespace('global', Path(__file__).parent / 'defaults.yml', required=True, modifiable=False) 74 | for k, v in defaults.config.items(): 75 | global_.config.setdefault(k, v) 76 | default_groups.update(defaults.config) 77 | 78 | # 加载插件预设 79 | from .plugin import plugins 80 | for name, handler in plugins.items(): 81 | if handler.preset_: 82 | namespace = get_namespace(name, True, handler.preset_) 83 | namespace.auto_decorate = handler.decorate_ 84 | plugin_namespaces.append(namespace) 85 | 86 | # 生成全局组默认配置 87 | if not global_.path.is_file(): 88 | global_.dirty = True 89 | global_.save() 90 | for name in ['group', 'user']: 91 | namespace = get_namespace(name, False) 92 | if not namespace.path.is_file(): 93 | namespace.add_group(42, 'Example') 94 | namespace.save() 95 | 96 | return True 97 | 98 | 99 | @nonebot_driver.on_shutdown 100 | @scheduler.scheduled_job('interval', minutes=5, coalesce=True, id='flexperm.save') 101 | def save_all() -> bool: 102 | """ 103 | 保存所有权限配置。 104 | 105 | :return: 是否全部保存成功。 106 | """ 107 | logger.debug('Saving permissions') 108 | failed = False 109 | for k, v in loaded.items(): 110 | try: 111 | v.save() 112 | except Exception as e: 113 | _ = e 114 | failed = True 115 | logger.exception('Failed to save namespace {}', k) 116 | return not failed 117 | 118 | 119 | class Namespace: 120 | """ 121 | 权限组名称空间。每个名称空间对应一个配置文件。 122 | """ 123 | 124 | auto_decorate: bool = False 125 | 126 | def __init__(self, namespace: str, path: Optional[Path], required: bool, modifiable: bool): 127 | self.name = namespace 128 | self.path = path 129 | self.groups: Dict[Union[str, int], PermissionGroup] = {} 130 | self.dirty = False 131 | self.modifiable = modifiable 132 | 133 | if not path: 134 | self.config = {} 135 | self.modifiable = False 136 | elif not required and not path.is_file(): 137 | self.config = CommentedMap() 138 | else: 139 | try: 140 | doc = yaml.load(path) 141 | except (OSError, YAMLError): 142 | logger.exception('Failed to load namespace {} ({})', namespace, path) 143 | doc = CommentedMap() 144 | 145 | if not isinstance(doc, CommentedMap): 146 | logger.error('Expect a dict: {} ({})', namespace, path) 147 | doc = CommentedMap() 148 | 149 | self.config: CommentedMap[Union[str, int], dict] = doc 150 | 151 | def save(self): 152 | """ 153 | 把本名称空间保存到硬盘上。若没有修改过则不做任何事。 154 | """ 155 | if self.modifiable and self.dirty: 156 | self.path.parent.mkdir(parents=True, exist_ok=True) 157 | yaml.dump(self.config, self.path) 158 | self.dirty = False 159 | 160 | def get_group(self, name: Union[str, int], referer: Optional["PermissionGroup"], required: bool 161 | ) -> "PermissionGroup": 162 | """ 163 | 获取本名称空间下的权限组。 164 | 165 | :param name: 组名。 166 | :param referer: 引用者。 167 | :param required: 权限组不存在时是否报错。 168 | :return: 169 | [0] 权限组,若失败则返回一个空组。
170 | [1] 是否成功。 171 | """ 172 | group = self.groups.get(name) 173 | if group is not None: 174 | if not group.referer: 175 | return group 176 | 177 | cycle = [f'{self.name}:{name}'] 178 | it = referer 179 | while it and not (it.name == name and it.namespace is self): 180 | cycle.append(it.qualified_name()) 181 | it = it.referer 182 | cycle.append(f'{self.name}:{name}') 183 | logger.error('Inheritance cycle detected: {}', ' -> '.join(reversed(cycle))) 184 | return NullPermissionGroup() 185 | 186 | group = self._get_group_uncached(name, referer, required) 187 | self.groups[name] = group 188 | return group 189 | 190 | def _get_group_uncached(self, name: Union[str, int], referer: Optional["PermissionGroup"], required: bool 191 | ) -> "PermissionGroup": 192 | group_desc = self.config.get(name) 193 | if group_desc is None: 194 | if required: 195 | if referer: 196 | logger.error('Permission group {}:{} not found (required from {})', 197 | self.name, name, referer.qualified_name()) 198 | else: 199 | logger.error('Permission group {}:{} not found', self.name, name) 200 | return NullPermissionGroup() 201 | 202 | try: 203 | desc = parse_obj_as(GroupDesc, group_desc) 204 | except ValueError: 205 | logger.exception('Failed to parse {}:{} ({})', self.name, name, self.path) 206 | return NullPermissionGroup() 207 | 208 | # 注入插件预设 209 | if self.name == 'global' and name in default_groups: 210 | for pn in plugin_namespaces: 211 | if name in pn.config: 212 | desc.inherits.append(f'{pn.name}:{name}') 213 | 214 | self.groups[name] = group = PermissionGroup(self, name) 215 | group.populate(desc, referer, self.name if self.auto_decorate else None) 216 | return group 217 | 218 | @contextmanager 219 | def modifying(self, name: Union[str, int] = None): 220 | if not self.modifiable: 221 | raise TypeError('Unmodifiable') 222 | yield self.config[name] if name is not None else None 223 | self.dirty = True 224 | 225 | def add_group(self, name: Union[str, int], comment: str = None): 226 | """ 227 | 创建权限组。 228 | 229 | :param name: 权限组名。 230 | :param comment: 注释。 231 | :raise KeyError: 权限组已存在。 232 | :raise TypeError: 名称空间不可修改。 233 | """ 234 | with self.modifying(): 235 | if name in self.config: 236 | raise KeyError('Duplicate group') 237 | self.config[name] = CommentedMap(permissions=CommentedSeq()) 238 | if comment is not None: 239 | self.config.yaml_add_eol_comment(comment, name) 240 | self.groups.pop(name, None) 241 | 242 | def remove_group(self, name: Union[str, int], force: bool): 243 | """ 244 | 移除权限组。 245 | 246 | :param name: 权限组名。 247 | :param force: 是否允许移除非空的权限组。 248 | :raise KeyError: 权限组不存在。 249 | :raise ValueError: 因权限组非空而没有移除。 250 | :raise TypeError: 名称空间不可修改。 251 | """ 252 | with self.modifying(): 253 | if not force and any(self.config[name].values()): 254 | raise ValueError('Not empty') 255 | del self.config[name] 256 | self.groups.pop(name, None) 257 | 258 | 259 | class PermissionGroup: 260 | """ 261 | 权限组。 262 | """ 263 | 264 | # 仅在加载过程中有效,加载完成后恢复None。该权限组的引用者,若没有引用者则指向自己。 265 | referer: Optional["PermissionGroup"] = None 266 | is_valid: bool = True 267 | 268 | def __init__(self, namespace: Namespace, name: Union[str, int]): 269 | self.namespace = namespace 270 | self.name = name 271 | self.denies: Set[str] = set() 272 | self.allows: Set[str] = set() 273 | self.inherits: List[PermissionGroup] = [] 274 | self.cache: OrderedDict[str, CheckResult] = OrderedDict() 275 | 276 | def __repr__(self): 277 | return f'' 278 | 279 | def qualified_name(self): 280 | return f'{self.namespace.name}:{self.name}' 281 | 282 | def check(self, perm: str) -> Optional["CheckResult"]: 283 | """ 284 | 检查本权限组对指定权限的说明。 285 | 286 | :param perm: 权限。 287 | :return: 查找结果,若不包含则返回 None 。 288 | """ 289 | if perm in self.cache: 290 | self.cache.move_to_end(perm) 291 | return self.cache[perm] 292 | result = self._check_uncached(perm) 293 | if len(self.cache) > 127: 294 | self.cache.popitem(last=False) 295 | self.cache[perm] = result 296 | return result 297 | 298 | def _check_uncached(self, perm: str) -> Optional["CheckResult"]: 299 | if check_wildcard(perm, self.denies): 300 | return CheckResult.DENY 301 | if check_wildcard(perm, self.allows): 302 | return CheckResult.ALLOW 303 | 304 | allowed = False 305 | for inherit in self.inherits: 306 | r = inherit.check(perm) 307 | if r == CheckResult.DENY: 308 | return r 309 | elif r == CheckResult.ALLOW: 310 | allowed = True 311 | if allowed: 312 | return CheckResult.ALLOW 313 | 314 | def populate(self, desc: "GroupDesc", referer: Optional["PermissionGroup"], decorate_base: Optional[str]): 315 | """ 316 | 从描述中读取权限组内容,同时会加载依赖的组。 317 | 318 | :param desc: 权限组描述。 319 | :param referer: 引用者。 320 | :param decorate_base: 如果需要修饰,插件名。 321 | """ 322 | self.referer = referer or self 323 | 324 | for parent in desc.inherits: 325 | namespace, group = parse_qualified_group_name(parent, self.namespace.name) 326 | res = get(namespace, group, self, True) 327 | if res.is_valid: 328 | self.inherits.append(res) 329 | 330 | for item in desc.permissions: 331 | if item.startswith('-'): 332 | target = self.denies 333 | item = item[1:] 334 | else: 335 | target = self.allows 336 | if decorate_base is not None: 337 | [item] = decorate_permission(decorate_base, [item]) 338 | target.add(item) 339 | 340 | del self.referer 341 | 342 | def add(self, item: str, comment: str = None): 343 | """ 344 | 添加权限描述。 345 | 346 | :param item: 权限描述。 347 | :param comment: 注释。 348 | :raise ValueError: 权限组中已有指定描述。 349 | :raise TypeError: 权限组不可修改。 350 | """ 351 | with self.namespace.modifying(self.name) as desc: 352 | if item.startswith('-'): 353 | target = self.denies 354 | perm = item[1:] 355 | else: 356 | target = self.allows 357 | perm = item 358 | if perm in target: 359 | raise ValueError('Duplicate item') 360 | target.add(perm) 361 | self.cache.clear() 362 | permissions: CommentedSeq = desc.setdefault('permissions', CommentedSeq()) 363 | permissions.append(item) 364 | if comment is not None: 365 | permissions.yaml_add_eol_comment(comment, len(permissions) - 1) # yaml_add_eol_comment 不支持负数下标 366 | 367 | def remove(self, item: str): 368 | """ 369 | 移除权限描述。 370 | 371 | :param item: 权限描述。 372 | :raise ValueError: 权限组中没有指定描述。 373 | :raise TypeError: 权限组不可修改。 374 | """ 375 | with self.namespace.modifying(self.name) as desc: 376 | if item.startswith('-'): 377 | target = self.denies 378 | perm = item[1:] 379 | else: 380 | target = self.allows 381 | perm = item 382 | if perm not in target: 383 | raise ValueError('No such item') 384 | target.remove(perm) 385 | self.cache.clear() 386 | permissions = desc['permissions'] 387 | permissions.remove(item) 388 | 389 | def add_inheritance(self, target: "PermissionGroup", comment: str = None): 390 | """ 391 | 添加继承关系。 392 | 393 | :param target: 需继承权限组。 394 | :param comment: 注释。 395 | :raise ValueError: 权限组中已有指定继承关系。 396 | :raise TypeError: 权限组不可修改。 397 | """ 398 | with self.namespace.modifying(self.name) as desc: 399 | if target in self.inherits: 400 | raise ValueError('Duplicate inheritance') 401 | self.inherits.append(target) 402 | self.cache.clear() 403 | inherits: CommentedSeq = desc.setdefault('inherits', CommentedSeq()) 404 | inherits.append(target.qualified_name()) 405 | if comment is not None: 406 | inherits.yaml_add_eol_comment(comment, len(inherits) - 1) # yaml_add_eol_comment 不支持负数下标 407 | 408 | def remove_inheritance(self, target: "PermissionGroup"): 409 | """ 410 | 移除继承关系。 411 | 412 | :param target: 需移除继承权限组。 413 | :raise ValueError: 权限组中没有指定继承关系。 414 | :raise TypeError: 权限组不可修改。 415 | """ 416 | with self.namespace.modifying(self.name) as desc: 417 | if target not in self.inherits: 418 | raise ValueError('No such inheritance') 419 | self.inherits.remove(target) 420 | self.cache.clear() 421 | 422 | inherits: CommentedSeq = desc.setdefault('inherits', CommentedSeq()) 423 | possible_decls = [target.qualified_name()] 424 | if target.namespace is self.namespace: 425 | possible_decls.append(target.name) 426 | for decl in possible_decls: 427 | with contextlib.suppress(ValueError): 428 | inherits.remove(decl) 429 | break 430 | 431 | 432 | class GroupDesc(BaseModel): 433 | """ 434 | 权限组描述。对应配置文件。 435 | """ 436 | 437 | permissions: List[str] = [] 438 | """ 439 | 授予或拒绝的权限列表。 440 | """ 441 | 442 | inherits: List[str] = [] 443 | """ 444 | 继承的权限组,每个元素为一个权限组名,可以表示为限定名(名称空间:组名),也可以不包含冒号,表示当前名称空间。 445 | """ 446 | 447 | class Config: 448 | extra = 'forbid' 449 | 450 | 451 | class CheckResult(Enum): 452 | ALLOW = 1 453 | DENY = 2 454 | 455 | 456 | def check_wildcard(item: str, set_: Set[str]) -> bool: 457 | if item in set_: 458 | return True 459 | segments = item.split('.') 460 | segments.append('*') 461 | while segments: 462 | segments[-1] = '*' 463 | if '.'.join(segments) in set_: 464 | return True 465 | segments.pop() 466 | return False 467 | 468 | 469 | def parse_qualified_group_name(qn: str, default_namespace: str = 'global') -> Tuple[str, Union[str, int]]: 470 | split = qn.split(':', maxsplit=1) 471 | if len(split) == 1: 472 | namespace, group = default_namespace, split[0] 473 | else: 474 | namespace, group = split 475 | if namespace in ['group', 'user']: 476 | group = try_int(group) 477 | return namespace, group 478 | 479 | 480 | def decorate_permission(base: str, perm: Iterable[str]) -> List[str]: 481 | result = [] 482 | for p in perm: 483 | if not p: 484 | result.append(base) 485 | elif p.startswith('/'): 486 | result.append(p[1:]) 487 | elif p.startswith('.'): 488 | prev = result[-1] if result else base 489 | result.append(prev + p) 490 | else: 491 | result.append(base + '.' + p) 492 | return result 493 | 494 | 495 | if TYPE_CHECKING: 496 | class NullPermissionGroup(PermissionGroup): 497 | def __new__(cls, *args, **kwargs): 498 | raise TypeError 499 | 500 | else: 501 | class NullPermissionGroup: 502 | referer = None 503 | namespace = None 504 | is_valid = False 505 | 506 | def __init__(self): 507 | pass 508 | 509 | def __repr__(self): 510 | return f'' 511 | 512 | def check(self, perm): 513 | pass 514 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.5.0 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "apscheduler" 5 | version = "3.10.1" 6 | description = "In-process task scheduler with Cron-like capabilities" 7 | optional = false 8 | python-versions = ">=3.6" 9 | files = [ 10 | {file = "APScheduler-3.10.1-py3-none-any.whl", hash = "sha256:e813ad5ada7aff36fb08cdda746b520531eaac7757832abc204868ba78e0c8f6"}, 11 | {file = "APScheduler-3.10.1.tar.gz", hash = "sha256:0293937d8f6051a0f493359440c1a1b93e882c57daf0197afeff0e727777b96e"}, 12 | ] 13 | 14 | [package.dependencies] 15 | pytz = "*" 16 | setuptools = ">=0.7" 17 | six = ">=1.4.0" 18 | tzlocal = ">=2.0,<3.dev0 || >=4.dev0" 19 | 20 | [package.extras] 21 | doc = ["sphinx", "sphinx-rtd-theme"] 22 | gevent = ["gevent"] 23 | mongodb = ["pymongo (>=3.0)"] 24 | redis = ["redis (>=3.0)"] 25 | rethinkdb = ["rethinkdb (>=2.4.0)"] 26 | sqlalchemy = ["sqlalchemy (>=1.4)"] 27 | testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"] 28 | tornado = ["tornado (>=4.3)"] 29 | twisted = ["twisted"] 30 | zookeeper = ["kazoo"] 31 | 32 | [[package]] 33 | name = "backports-zoneinfo" 34 | version = "0.2.1" 35 | description = "Backport of the standard library zoneinfo module" 36 | optional = false 37 | python-versions = ">=3.6" 38 | files = [ 39 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, 40 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, 41 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, 42 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, 43 | {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, 44 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, 45 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, 46 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, 47 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, 48 | {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, 49 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, 50 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, 51 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, 52 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, 53 | {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, 54 | {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, 55 | ] 56 | 57 | [package.extras] 58 | tzdata = ["tzdata"] 59 | 60 | [[package]] 61 | name = "colorama" 62 | version = "0.4.6" 63 | description = "Cross-platform colored terminal text." 64 | optional = false 65 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 66 | files = [ 67 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 68 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 69 | ] 70 | 71 | [[package]] 72 | name = "idna" 73 | version = "3.4" 74 | description = "Internationalized Domain Names in Applications (IDNA)" 75 | optional = false 76 | python-versions = ">=3.5" 77 | files = [ 78 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 79 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 80 | ] 81 | 82 | [[package]] 83 | name = "loguru" 84 | version = "0.7.0" 85 | description = "Python logging made (stupidly) simple" 86 | optional = false 87 | python-versions = ">=3.5" 88 | files = [ 89 | {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, 90 | {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, 91 | ] 92 | 93 | [package.dependencies] 94 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 95 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 96 | 97 | [package.extras] 98 | dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] 99 | 100 | [[package]] 101 | name = "msgpack" 102 | version = "1.0.5" 103 | description = "MessagePack serializer" 104 | optional = false 105 | python-versions = "*" 106 | files = [ 107 | {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, 108 | {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, 109 | {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, 110 | {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, 111 | {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, 112 | {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, 113 | {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, 114 | {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, 115 | {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, 116 | {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, 117 | {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, 118 | {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, 119 | {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, 120 | {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, 121 | {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, 122 | {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, 123 | {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, 124 | {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, 125 | {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, 126 | {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, 127 | {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, 128 | {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, 129 | {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, 130 | {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, 131 | {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, 132 | {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, 133 | {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, 134 | {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, 135 | {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, 136 | {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, 137 | {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, 138 | {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, 139 | {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, 140 | {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, 141 | {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, 142 | {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, 143 | {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, 144 | {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, 145 | {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, 146 | {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, 147 | {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, 148 | {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, 149 | {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, 150 | {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, 151 | {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, 152 | {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, 153 | {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, 154 | {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, 155 | {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, 156 | {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, 157 | {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, 158 | {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, 159 | {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, 160 | {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, 161 | {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, 162 | {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, 163 | {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, 164 | {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, 165 | {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, 166 | {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, 167 | {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, 168 | {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, 169 | {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, 170 | ] 171 | 172 | [[package]] 173 | name = "multidict" 174 | version = "6.0.4" 175 | description = "multidict implementation" 176 | optional = false 177 | python-versions = ">=3.7" 178 | files = [ 179 | {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, 180 | {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, 181 | {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, 182 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, 183 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, 184 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, 185 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, 186 | {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, 187 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, 188 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, 189 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, 190 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, 191 | {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, 192 | {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, 193 | {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, 194 | {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, 195 | {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, 196 | {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, 197 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, 198 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, 199 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, 200 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, 201 | {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, 202 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, 203 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, 204 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, 205 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, 206 | {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, 207 | {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, 208 | {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, 209 | {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, 210 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, 211 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, 212 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, 213 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, 214 | {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, 215 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, 216 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, 217 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, 218 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, 219 | {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, 220 | {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, 221 | {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, 222 | {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, 223 | {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, 224 | {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, 225 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, 226 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, 227 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, 228 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, 229 | {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, 230 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, 231 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, 232 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, 233 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, 234 | {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, 235 | {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, 236 | {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, 237 | {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, 238 | {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, 239 | {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, 240 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, 241 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, 242 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, 243 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, 244 | {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, 245 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, 246 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, 247 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, 248 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, 249 | {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, 250 | {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, 251 | {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, 252 | {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, 253 | ] 254 | 255 | [[package]] 256 | name = "nonebot-adapter-onebot" 257 | version = "2.2.3" 258 | description = "OneBot(CQHTTP) adapter for nonebot2" 259 | optional = false 260 | python-versions = ">=3.8,<4.0" 261 | files = [ 262 | {file = "nonebot_adapter_onebot-2.2.3-py3-none-any.whl", hash = "sha256:e715e05578cb58cf42adddb0bac6c060a6ea7c9b2f18e6f011786bbc2bc8d0bd"}, 263 | {file = "nonebot_adapter_onebot-2.2.3.tar.gz", hash = "sha256:e0ac165536a24f7c5e620092ab596022e8f85d462c17ec8d7a17b191093b4859"}, 264 | ] 265 | 266 | [package.dependencies] 267 | msgpack = ">=1.0.3,<2.0.0" 268 | nonebot2 = ">=2.0.0-beta.3,<3.0.0" 269 | 270 | [[package]] 271 | name = "nonebot-plugin-apscheduler" 272 | version = "0.2.0" 273 | description = "APScheduler Support for NoneBot2" 274 | optional = false 275 | python-versions = ">=3.8,<4.0" 276 | files = [ 277 | {file = "nonebot-plugin-apscheduler-0.2.0.tar.gz", hash = "sha256:7b63e99a611b657533b48fcf1f8c6627c18c2eb3fa820a906cd4ec4666c0ceb0"}, 278 | {file = "nonebot_plugin_apscheduler-0.2.0-py3-none-any.whl", hash = "sha256:9285ee84ca1cfa4db73c86cedb5911bbbd25a21ec0cd5f22447cd12f89e48fb4"}, 279 | ] 280 | 281 | [package.dependencies] 282 | apscheduler = ">=3.7.0,<4.0.0" 283 | nonebot2 = ">=2.0.0-rc.1,<3.0.0" 284 | 285 | [[package]] 286 | name = "nonebot2" 287 | version = "2.0.0" 288 | description = "An asynchronous python bot framework." 289 | optional = false 290 | python-versions = ">=3.8,<4.0" 291 | files = [ 292 | {file = "nonebot2-2.0.0-py3-none-any.whl", hash = "sha256:a3b0caedd52033a11f1d7c24875c3b89513a4b5014f703e0bb266e2e39a0bd30"}, 293 | {file = "nonebot2-2.0.0.tar.gz", hash = "sha256:144c175ce100c3300d53475fc47b7a9f0a6545861ff12bdc8a1442ab12d430df"}, 294 | ] 295 | 296 | [package.dependencies] 297 | loguru = ">=0.6.0,<1.0.0" 298 | pydantic = {version = ">=1.10.0,<2.0.0", extras = ["dotenv"]} 299 | pygtrie = ">=2.4.1,<3.0.0" 300 | tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} 301 | typing-extensions = ">=4.0.0,<5.0.0" 302 | yarl = ">=1.7.2,<2.0.0" 303 | 304 | [package.extras] 305 | aiohttp = ["aiohttp[speedups] (>=3.7.4,<4.0.0)"] 306 | all = ["Quart (>=0.18.0,<1.0.0)", "aiohttp[speedups] (>=3.7.4,<4.0.0)", "fastapi (>=0.93.0,<1.0.0)", "httpx[http2] (>=0.20.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)", "websockets (>=10.0)"] 307 | fastapi = ["fastapi (>=0.93.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"] 308 | httpx = ["httpx[http2] (>=0.20.0,<1.0.0)"] 309 | quart = ["Quart (>=0.18.0,<1.0.0)", "uvicorn[standard] (>=0.20.0,<1.0.0)"] 310 | websockets = ["websockets (>=10.0)"] 311 | 312 | [[package]] 313 | name = "pydantic" 314 | version = "1.10.9" 315 | description = "Data validation and settings management using python type hints" 316 | optional = false 317 | python-versions = ">=3.7" 318 | files = [ 319 | {file = "pydantic-1.10.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e692dec4a40bfb40ca530e07805b1208c1de071a18d26af4a2a0d79015b352ca"}, 320 | {file = "pydantic-1.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c52eb595db83e189419bf337b59154bdcca642ee4b2a09e5d7797e41ace783f"}, 321 | {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939328fd539b8d0edf244327398a667b6b140afd3bf7e347cf9813c736211896"}, 322 | {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b48d3d634bca23b172f47f2335c617d3fcb4b3ba18481c96b7943a4c634f5c8d"}, 323 | {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f0b7628fb8efe60fe66fd4adadd7ad2304014770cdc1f4934db41fe46cc8825f"}, 324 | {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e1aa5c2410769ca28aa9a7841b80d9d9a1c5f223928ca8bec7e7c9a34d26b1d4"}, 325 | {file = "pydantic-1.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:eec39224b2b2e861259d6f3c8b6290d4e0fbdce147adb797484a42278a1a486f"}, 326 | {file = "pydantic-1.10.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d111a21bbbfd85c17248130deac02bbd9b5e20b303338e0dbe0faa78330e37e0"}, 327 | {file = "pydantic-1.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e9aec8627a1a6823fc62fb96480abe3eb10168fd0d859ee3d3b395105ae19a7"}, 328 | {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07293ab08e7b4d3c9d7de4949a0ea571f11e4557d19ea24dd3ae0c524c0c334d"}, 329 | {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee829b86ce984261d99ff2fd6e88f2230068d96c2a582f29583ed602ef3fc2c"}, 330 | {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b466a23009ff5cdd7076eb56aca537c745ca491293cc38e72bf1e0e00de5b91"}, 331 | {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7847ca62e581e6088d9000f3c497267868ca2fa89432714e21a4fb33a04d52e8"}, 332 | {file = "pydantic-1.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:7845b31959468bc5b78d7b95ec52fe5be32b55d0d09983a877cca6aedc51068f"}, 333 | {file = "pydantic-1.10.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:517a681919bf880ce1dac7e5bc0c3af1e58ba118fd774da2ffcd93c5f96eaece"}, 334 | {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67195274fd27780f15c4c372f4ba9a5c02dad6d50647b917b6a92bf00b3d301a"}, 335 | {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2196c06484da2b3fded1ab6dbe182bdabeb09f6318b7fdc412609ee2b564c49a"}, 336 | {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6257bb45ad78abacda13f15bde5886efd6bf549dd71085e64b8dcf9919c38b60"}, 337 | {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3283b574b01e8dbc982080d8287c968489d25329a463b29a90d4157de4f2baaf"}, 338 | {file = "pydantic-1.10.9-cp37-cp37m-win_amd64.whl", hash = "sha256:5f8bbaf4013b9a50e8100333cc4e3fa2f81214033e05ac5aa44fa24a98670a29"}, 339 | {file = "pydantic-1.10.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9cd67fb763248cbe38f0593cd8611bfe4b8ad82acb3bdf2b0898c23415a1f82"}, 340 | {file = "pydantic-1.10.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f50e1764ce9353be67267e7fd0da08349397c7db17a562ad036aa7c8f4adfdb6"}, 341 | {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73ef93e5e1d3c8e83f1ff2e7fdd026d9e063c7e089394869a6e2985696693766"}, 342 | {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128d9453d92e6e81e881dd7e2484e08d8b164da5507f62d06ceecf84bf2e21d3"}, 343 | {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ad428e92ab68798d9326bb3e5515bc927444a3d71a93b4a2ca02a8a5d795c572"}, 344 | {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fab81a92f42d6d525dd47ced310b0c3e10c416bbfae5d59523e63ea22f82b31e"}, 345 | {file = "pydantic-1.10.9-cp38-cp38-win_amd64.whl", hash = "sha256:963671eda0b6ba6926d8fc759e3e10335e1dc1b71ff2a43ed2efd6996634dafb"}, 346 | {file = "pydantic-1.10.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:970b1bdc6243ef663ba5c7e36ac9ab1f2bfecb8ad297c9824b542d41a750b298"}, 347 | {file = "pydantic-1.10.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7e1d5290044f620f80cf1c969c542a5468f3656de47b41aa78100c5baa2b8276"}, 348 | {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83fcff3c7df7adff880622a98022626f4f6dbce6639a88a15a3ce0f96466cb60"}, 349 | {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0da48717dc9495d3a8f215e0d012599db6b8092db02acac5e0d58a65248ec5bc"}, 350 | {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0a2aabdc73c2a5960e87c3ffebca6ccde88665616d1fd6d3db3178ef427b267a"}, 351 | {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9863b9420d99dfa9c064042304868e8ba08e89081428a1c471858aa2af6f57c4"}, 352 | {file = "pydantic-1.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:e7c9900b43ac14110efa977be3da28931ffc74c27e96ee89fbcaaf0b0fe338e1"}, 353 | {file = "pydantic-1.10.9-py3-none-any.whl", hash = "sha256:6cafde02f6699ce4ff643417d1a9223716ec25e228ddc3b436fe7e2d25a1f305"}, 354 | {file = "pydantic-1.10.9.tar.gz", hash = "sha256:95c70da2cd3b6ddf3b9645ecaa8d98f3d80c606624b6d245558d202cd23ea3be"}, 355 | ] 356 | 357 | [package.dependencies] 358 | python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""} 359 | typing-extensions = ">=4.2.0" 360 | 361 | [package.extras] 362 | dotenv = ["python-dotenv (>=0.10.4)"] 363 | email = ["email-validator (>=1.0.3)"] 364 | 365 | [[package]] 366 | name = "pygtrie" 367 | version = "2.5.0" 368 | description = "A pure Python trie data structure implementation." 369 | optional = false 370 | python-versions = "*" 371 | files = [ 372 | {file = "pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16"}, 373 | {file = "pygtrie-2.5.0.tar.gz", hash = "sha256:203514ad826eb403dab1d2e2ddd034e0d1534bbe4dbe0213bb0593f66beba4e2"}, 374 | ] 375 | 376 | [[package]] 377 | name = "python-dotenv" 378 | version = "1.0.0" 379 | description = "Read key-value pairs from a .env file and set them as environment variables" 380 | optional = false 381 | python-versions = ">=3.8" 382 | files = [ 383 | {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, 384 | {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, 385 | ] 386 | 387 | [package.extras] 388 | cli = ["click (>=5.0)"] 389 | 390 | [[package]] 391 | name = "pytz" 392 | version = "2023.3" 393 | description = "World timezone definitions, modern and historical" 394 | optional = false 395 | python-versions = "*" 396 | files = [ 397 | {file = "pytz-2023.3-py2.py3-none-any.whl", hash = "sha256:a151b3abb88eda1d4e34a9814df37de2a80e301e68ba0fd856fb9b46bfbbbffb"}, 398 | {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, 399 | ] 400 | 401 | [[package]] 402 | name = "ruamel-yaml" 403 | version = "0.17.31" 404 | description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" 405 | optional = false 406 | python-versions = ">=3" 407 | files = [ 408 | {file = "ruamel.yaml-0.17.31-py3-none-any.whl", hash = "sha256:3cf153f0047ced526e723097ac615d3009371779432e304dbd5596b6f3a4c777"}, 409 | {file = "ruamel.yaml-0.17.31.tar.gz", hash = "sha256:098ed1eb6d338a684891a72380277c1e6fc4d4ae0e120de9a447275056dda335"}, 410 | ] 411 | 412 | [package.dependencies] 413 | "ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.12\""} 414 | 415 | [package.extras] 416 | docs = ["ryd"] 417 | jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"] 418 | 419 | [[package]] 420 | name = "ruamel-yaml-clib" 421 | version = "0.2.7" 422 | description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" 423 | optional = false 424 | python-versions = ">=3.5" 425 | files = [ 426 | {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71"}, 427 | {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7"}, 428 | {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80"}, 429 | {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab"}, 430 | {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win32.whl", hash = "sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231"}, 431 | {file = "ruamel.yaml.clib-0.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a"}, 432 | {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e"}, 433 | {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81"}, 434 | {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf"}, 435 | {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94"}, 436 | {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win32.whl", hash = "sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38"}, 437 | {file = "ruamel.yaml.clib-0.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122"}, 438 | {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072"}, 439 | {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-macosx_12_0_arm64.whl", hash = "sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8"}, 440 | {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3"}, 441 | {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763"}, 442 | {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win32.whl", hash = "sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e"}, 443 | {file = "ruamel.yaml.clib-0.2.7-cp36-cp36m-win_amd64.whl", hash = "sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646"}, 444 | {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f"}, 445 | {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0"}, 446 | {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282"}, 447 | {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7"}, 448 | {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win32.whl", hash = "sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93"}, 449 | {file = "ruamel.yaml.clib-0.2.7-cp37-cp37m-win_amd64.whl", hash = "sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b"}, 450 | {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb"}, 451 | {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307"}, 452 | {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697"}, 453 | {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b"}, 454 | {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win32.whl", hash = "sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac"}, 455 | {file = "ruamel.yaml.clib-0.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f"}, 456 | {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9"}, 457 | {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1"}, 458 | {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640"}, 459 | {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b"}, 460 | {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win32.whl", hash = "sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8"}, 461 | {file = "ruamel.yaml.clib-0.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5"}, 462 | {file = "ruamel.yaml.clib-0.2.7.tar.gz", hash = "sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497"}, 463 | ] 464 | 465 | [[package]] 466 | name = "setuptools" 467 | version = "67.8.0" 468 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 469 | optional = false 470 | python-versions = ">=3.7" 471 | files = [ 472 | {file = "setuptools-67.8.0-py3-none-any.whl", hash = "sha256:5df61bf30bb10c6f756eb19e7c9f3b473051f48db77fddbe06ff2ca307df9a6f"}, 473 | {file = "setuptools-67.8.0.tar.gz", hash = "sha256:62642358adc77ffa87233bc4d2354c4b2682d214048f500964dbe760ccedf102"}, 474 | ] 475 | 476 | [package.extras] 477 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 478 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 479 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 480 | 481 | [[package]] 482 | name = "six" 483 | version = "1.16.0" 484 | description = "Python 2 and 3 compatibility utilities" 485 | optional = false 486 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 487 | files = [ 488 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 489 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 490 | ] 491 | 492 | [[package]] 493 | name = "tomli" 494 | version = "2.0.1" 495 | description = "A lil' TOML parser" 496 | optional = false 497 | python-versions = ">=3.7" 498 | files = [ 499 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 500 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 501 | ] 502 | 503 | [[package]] 504 | name = "typing-extensions" 505 | version = "4.6.3" 506 | description = "Backported and Experimental Type Hints for Python 3.7+" 507 | optional = false 508 | python-versions = ">=3.7" 509 | files = [ 510 | {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"}, 511 | {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"}, 512 | ] 513 | 514 | [[package]] 515 | name = "tzdata" 516 | version = "2023.3" 517 | description = "Provider of IANA time zone data" 518 | optional = false 519 | python-versions = ">=2" 520 | files = [ 521 | {file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"}, 522 | {file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"}, 523 | ] 524 | 525 | [[package]] 526 | name = "tzlocal" 527 | version = "5.0.1" 528 | description = "tzinfo object for the local timezone" 529 | optional = false 530 | python-versions = ">=3.7" 531 | files = [ 532 | {file = "tzlocal-5.0.1-py3-none-any.whl", hash = "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"}, 533 | {file = "tzlocal-5.0.1.tar.gz", hash = "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803"}, 534 | ] 535 | 536 | [package.dependencies] 537 | "backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} 538 | tzdata = {version = "*", markers = "platform_system == \"Windows\""} 539 | 540 | [package.extras] 541 | devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] 542 | 543 | [[package]] 544 | name = "win32-setctime" 545 | version = "1.1.0" 546 | description = "A small Python utility to set file creation time on Windows" 547 | optional = false 548 | python-versions = ">=3.5" 549 | files = [ 550 | {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"}, 551 | {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"}, 552 | ] 553 | 554 | [package.extras] 555 | dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"] 556 | 557 | [[package]] 558 | name = "yarl" 559 | version = "1.9.2" 560 | description = "Yet another URL library" 561 | optional = false 562 | python-versions = ">=3.7" 563 | files = [ 564 | {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, 565 | {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, 566 | {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, 567 | {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, 568 | {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, 569 | {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, 570 | {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, 571 | {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, 572 | {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, 573 | {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, 574 | {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, 575 | {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, 576 | {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, 577 | {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, 578 | {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, 579 | {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, 580 | {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, 581 | {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, 582 | {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, 583 | {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, 584 | {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, 585 | {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, 586 | {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, 587 | {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, 588 | {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, 589 | {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, 590 | {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, 591 | {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, 592 | {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, 593 | {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, 594 | {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, 595 | {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, 596 | {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, 597 | {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, 598 | {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, 599 | {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, 600 | {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, 601 | {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, 602 | {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, 603 | {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, 604 | {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, 605 | {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, 606 | {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, 607 | {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, 608 | {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, 609 | {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, 610 | {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, 611 | {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, 612 | {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, 613 | {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, 614 | {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, 615 | {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, 616 | {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, 617 | {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, 618 | {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, 619 | {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, 620 | {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, 621 | {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, 622 | {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, 623 | {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, 624 | {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, 625 | {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, 626 | {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, 627 | {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, 628 | {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, 629 | {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, 630 | {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, 631 | {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, 632 | {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, 633 | {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, 634 | {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, 635 | {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, 636 | {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, 637 | {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, 638 | ] 639 | 640 | [package.dependencies] 641 | idna = ">=2.0" 642 | multidict = ">=4.0" 643 | 644 | [metadata] 645 | lock-version = "2.0" 646 | python-versions = "^3.8" 647 | content-hash = "241269b139fdf1df40155198b9e2d3141dbfb6764dac11411e8e891826d88313" 648 | --------------------------------------------------------------------------------