diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d0072d5..5d18d81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/lang/zh-CN/ ### Added - 通过名称加主页避免机器人重复 +- 商店存放 json5 格式的数据 ### Fixed diff --git a/examples/noneflow.yml b/examples/noneflow.yml index adad380f..9c95d359 100644 --- a/examples/noneflow.yml +++ b/examples/noneflow.yml @@ -43,9 +43,9 @@ jobs: config: > { "base": "master", - "plugin_path": "assets/plugins.json", - "bot_path": "assets/bots.json", - "adapter_path": "assets/adapters.json", + "plugin_path": "assets/plugins.json5", + "bot_path": "assets/bots.json5", + "adapter_path": "assets/adapters.json5", "registry_repository": "nonebot/registry" } env: diff --git a/pyproject.toml b/pyproject.toml index ce8ddffa..962fca56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "nonebot2>=2.4.0", "pre-commit>=4.0.1", "pydantic-extra-types>=2.9.0", + "pyjson5>=1.6.7", ] [project.urls] diff --git a/src/plugins/github/plugins/publish/utils.py b/src/plugins/github/plugins/publish/utils.py index 24431769..ce8748a5 100644 --- a/src/plugins/github/plugins/publish/utils.py +++ b/src/plugins/github/plugins/publish/utils.py @@ -10,8 +10,9 @@ from src.plugins.github.models import IssueHandler from src.plugins.github.models.github import GithubHandler from src.plugins.github.utils import commit_message as _commit_message -from src.plugins.github.utils import dump_json, load_json, run_shell_command +from src.plugins.github.utils import run_shell_command from src.providers.models import RegistryUpdatePayload, to_store +from src.providers.utils import dump_json5, load_json5_from_file from src.providers.validation import PublishType, ValidationDict from .constants import ( @@ -149,9 +150,9 @@ def update_file(result: ValidationDict) -> None: logger.info(f"正在更新文件: {path}") - data = load_json(path) + data = load_json5_from_file(path) data.append(new_data) - dump_json(path, data, 2) + dump_json5(path, data) logger.info("文件更新完成") diff --git a/src/plugins/github/plugins/remove/utils.py b/src/plugins/github/plugins/remove/utils.py index c07a5998..83e865cd 100644 --- a/src/plugins/github/plugins/remove/utils.py +++ b/src/plugins/github/plugins/remove/utils.py @@ -9,11 +9,8 @@ get_type_by_labels, ) from src.plugins.github.models import GithubHandler, IssueHandler -from src.plugins.github.utils import ( - commit_message, - dump_json, - run_shell_command, -) +from src.plugins.github.utils import commit_message, run_shell_command +from src.providers.utils import dump_json5 from src.providers.validation.models import PublishType from .constants import COMMIT_MESSAGE_PREFIX, REMOVE_LABEL @@ -40,7 +37,7 @@ def update_file(type: PublishType, key: str): data = load_publish_data(type) # 删除对应的数据项 data.pop(key) - dump_json(path, list(data.values())) + dump_json5(path, list(data.values())) logger.info(f"已更新 {path.name} 文件") diff --git a/src/plugins/github/plugins/remove/validation.py b/src/plugins/github/plugins/remove/validation.py index 9f6d06b7..59fa257a 100644 --- a/src/plugins/github/plugins/remove/validation.py +++ b/src/plugins/github/plugins/remove/validation.py @@ -4,8 +4,9 @@ from src.plugins.github import plugin_config from src.plugins.github.models import AuthorInfo -from src.plugins.github.utils import extract_issue_info_from_issue, load_json +from src.plugins.github.utils import extract_issue_info_from_issue from src.providers.constants import BOT_KEY_TEMPLATE, PYPI_KEY_TEMPLATE +from src.providers.utils import load_json5_from_file from src.providers.validation.models import PublishType from .constants import ( @@ -25,7 +26,9 @@ def load_publish_data(publish_type: PublishType): project_link=adapter["project_link"], module_name=adapter["module_name"], ): adapter - for adapter in load_json(plugin_config.input_config.adapter_path) + for adapter in load_json5_from_file( + plugin_config.input_config.adapter_path + ) } case PublishType.BOT: return { @@ -33,7 +36,7 @@ def load_publish_data(publish_type: PublishType): name=bot["name"], homepage=bot["homepage"], ): bot - for bot in load_json(plugin_config.input_config.bot_path) + for bot in load_json5_from_file(plugin_config.input_config.bot_path) } case PublishType.PLUGIN: return { @@ -41,7 +44,9 @@ def load_publish_data(publish_type: PublishType): project_link=plugin["project_link"], module_name=plugin["module_name"], ): plugin - for plugin in load_json(plugin_config.input_config.plugin_path) + for plugin in load_json5_from_file( + plugin_config.input_config.plugin_path + ) } case PublishType.DRIVER: raise ValueError("不支持的删除类型") diff --git a/src/plugins/github/utils.py b/src/plugins/github/utils.py index 12a66a56..723baa72 100644 --- a/src/plugins/github/utils.py +++ b/src/plugins/github/utils.py @@ -1,11 +1,7 @@ -import json import subprocess -from pathlib import Path from re import Pattern -from typing import Any from nonebot import logger -from pydantic_core import to_jsonable_python def run_shell_command(command: list[str]): @@ -30,20 +26,6 @@ def commit_message(prefix: str, message: str, issue_number: int): return f"{prefix} {message} (#{issue_number})" -def load_json(path: Path) -> list[dict[str, str]]: - """加载 JSON 文件""" - with path.open("r", encoding="utf-8") as f: - return json.load(f) - - -def dump_json(path: Path, data: Any, indent: int = 4): - """保存 JSON 文件""" - with path.open("w", encoding="utf-8") as f: - # 结尾加上换行符,不然会被 pre-commit fix - json.dump(to_jsonable_python(data), f, ensure_ascii=False, indent=indent) - f.write("\n") - - def extract_issue_info_from_issue( patterns: dict[str, Pattern[str]], body: str ) -> dict[str, str | None]: diff --git a/src/providers/constants.py b/src/providers/constants.py index c14d3511..9261a891 100644 --- a/src/providers/constants.py +++ b/src/providers/constants.py @@ -25,10 +25,10 @@ os.environ.get("STORE_BASE_URL") or "https://raw.githubusercontent.com/nonebot/nonebot2/master/assets" ) -STORE_ADAPTERS_URL = f"{STORE_BASE_URL}/adapters.json" -STORE_BOTS_URL = f"{STORE_BASE_URL}/bots.json" -STORE_DRIVERS_URL = f"{STORE_BASE_URL}/drivers.json" -STORE_PLUGINS_URL = f"{STORE_BASE_URL}/plugins.json" +STORE_ADAPTERS_URL = f"{STORE_BASE_URL}/adapters.json5" +STORE_BOTS_URL = f"{STORE_BASE_URL}/bots.json5" +STORE_DRIVERS_URL = f"{STORE_BASE_URL}/drivers.json5" +STORE_PLUGINS_URL = f"{STORE_BASE_URL}/plugins.json5" """plugin_test.py 中也有一个常量,需要同时修改""" # 商店测试镜像 diff --git a/src/providers/docker_test/plugin_test.py b/src/providers/docker_test/plugin_test.py index 9d2cf75e..cf773c75 100644 --- a/src/providers/docker_test/plugin_test.py +++ b/src/providers/docker_test/plugin_test.py @@ -19,9 +19,11 @@ from urllib.request import urlopen # NoneBot Store -STORE_PLUGINS_URL = ( - "https://raw.githubusercontent.com/nonebot/nonebot2/master/assets/plugins.json" +STORE_BASE_URL = ( + os.environ.get("STORE_BASE_URL") + or "https://raw.githubusercontent.com/nonebot/nonebot2/master/assets" ) +STORE_PLUGINS_URL = f"{STORE_BASE_URL}/plugins.json5" # 匹配信息的正则表达式 ISSUE_PATTERN = r"### {}\s+([^\s#].*?)(?=(?:\s+###|$))" diff --git a/src/providers/store_test/store.py b/src/providers/store_test/store.py index 2e4f66f6..fce47b17 100644 --- a/src/providers/store_test/store.py +++ b/src/providers/store_test/store.py @@ -26,6 +26,7 @@ StorePlugin, StoreTestResult, ) +from src.providers.utils import dump_json, load_json5_from_web, load_json_from_web from src.providers.validation.utils import get_author_name from .constants import ( @@ -36,7 +37,7 @@ PLUGINS_PATH, RESULTS_PATH, ) -from .utils import dump_json, get_latest_version, load_json +from .utils import get_latest_version from .validation import validate_plugin print = click.echo @@ -52,63 +53,65 @@ def __init__(self) -> None: project_link=adapter["project_link"], module_name=adapter["module_name"], ): StoreAdapter(**adapter) - for adapter in load_json(STORE_ADAPTERS_URL) + for adapter in load_json5_from_web(STORE_ADAPTERS_URL) } self._store_bots: dict[str, StoreBot] = { BOT_KEY_TEMPLATE.format( name=bot["name"], homepage=bot["homepage"], ): StoreBot(**bot) - for bot in load_json(STORE_BOTS_URL) + for bot in load_json5_from_web(STORE_BOTS_URL) } self._store_drivers: dict[str, StoreDriver] = { PYPI_KEY_TEMPLATE.format( project_link=driver["project_link"], module_name=driver["module_name"], ): StoreDriver(**driver) - for driver in load_json(STORE_DRIVERS_URL) + for driver in load_json5_from_web(STORE_DRIVERS_URL) } self._store_plugins: dict[str, StorePlugin] = { PYPI_KEY_TEMPLATE.format( project_link=plugin["project_link"], module_name=plugin["module_name"], ): StorePlugin(**plugin) - for plugin in load_json(STORE_PLUGINS_URL) + for plugin in load_json5_from_web(STORE_PLUGINS_URL) } # 上次测试的结果 self._previous_results: dict[str, StoreTestResult] = { key: StoreTestResult(**value) - for key, value in load_json(REGISTRY_RESULTS_URL).items() + for key, value in load_json_from_web(REGISTRY_RESULTS_URL).items() } self._previous_adapters: dict[str, RegistryAdapter] = { PYPI_KEY_TEMPLATE.format( project_link=adapter["project_link"], module_name=adapter["module_name"], ): RegistryAdapter(**adapter) - for adapter in load_json(REGISTRY_ADAPTERS_URL) + for adapter in load_json_from_web(REGISTRY_ADAPTERS_URL) } self._previous_bots: dict[str, RegistryBot] = { BOT_KEY_TEMPLATE.format( name=bot["name"], homepage=bot["homepage"], ): RegistryBot(**bot) - for bot in load_json(url=REGISTRY_BOTS_URL) + for bot in load_json_from_web(url=REGISTRY_BOTS_URL) } self._previous_drivers: dict[str, RegistryDriver] = { PYPI_KEY_TEMPLATE.format( project_link=driver["project_link"], module_name=driver["module_name"], ): RegistryDriver(**driver) - for driver in load_json(REGISTRY_DRIVERS_URL) + for driver in load_json_from_web(REGISTRY_DRIVERS_URL) } self._previous_plugins: dict[str, RegistryPlugin] = { PYPI_KEY_TEMPLATE.format( project_link=plugin["project_link"], module_name=plugin["module_name"] ): RegistryPlugin(**plugin) - for plugin in load_json(REGISTRY_PLUGINS_URL) + for plugin in load_json_from_web(REGISTRY_PLUGINS_URL) } # 插件配置文件 - self._plugin_configs: dict[str, str] = load_json(REGISTRY_PLUGIN_CONFIG_URL) + self._plugin_configs: dict[str, str] = load_json_from_web( + REGISTRY_PLUGIN_CONFIG_URL + ) def should_skip(self, key: str, force: bool = False) -> bool: """是否跳过测试""" diff --git a/src/providers/store_test/utils.py b/src/providers/store_test/utils.py index 7ae7e0b5..0602cacc 100644 --- a/src/providers/store_test/utils.py +++ b/src/providers/store_test/utils.py @@ -1,31 +1,9 @@ -import json from functools import cache -from pathlib import Path from typing import Any import httpx -from pydantic_core import to_jsonable_python - -def load_json(url: str) -> Any: - """从网络加载 JSON 文件""" - r = httpx.get(url) - if r.status_code != 200: - raise ValueError(f"下载文件失败:{r.text}") - return r.json() - - -def dump_json(path: Path, data: Any, minify: bool = True) -> None: - """保存 JSON 文件 - - 为减少文件大小,还需手动设置 separators - """ - data = to_jsonable_python(data) - with open(path, "w", encoding="utf-8") as f: - if minify: - json.dump(data, f, ensure_ascii=False, separators=(",", ":")) - else: - json.dump(data, f, ensure_ascii=False, indent=2) +from src.providers.utils import load_json_from_web @cache @@ -55,5 +33,5 @@ def get_upload_time(project_link: str) -> str: def get_user_id(name: str) -> int: """获取用户信息""" - data = load_json(f"https://api.github.com/users/{name}") + data = load_json_from_web(f"https://api.github.com/users/{name}") return data["id"] diff --git a/src/providers/utils.py b/src/providers/utils.py new file mode 100644 index 00000000..aee93d07 --- /dev/null +++ b/src/providers/utils.py @@ -0,0 +1,65 @@ +import json +from pathlib import Path +from typing import Any + +import httpx +import pyjson5 +from pydantic_core import to_jsonable_python + + +def load_json_from_file(file_path: Path): + """从文件加载 JSON 文件""" + with open(file_path, encoding="utf-8") as file: + return json.load(file) + + +def load_json_from_web(url: str): + """从网络加载 JSON 文件""" + r = httpx.get(url) + if r.status_code != 200: + raise ValueError(f"下载文件失败:{r.text}") + return r.json() + + +def dump_json(path: Path, data: Any, minify: bool = True) -> None: + """保存 JSON 文件 + + 为减少文件大小,还需手动设置 separators + """ + data = to_jsonable_python(data) + with open(path, "w", encoding="utf-8") as f: + if minify: + json.dump(data, f, ensure_ascii=False, separators=(",", ":")) + else: + json.dump(data, f, ensure_ascii=False, indent=2) + + +def load_json5_from_file(file_path: Path): + """从文件加载 JSON5 文件""" + with open(file_path, encoding="utf-8") as file: + return pyjson5.decode_io(file) # type: ignore + + +def load_json5_from_web(url: str): + """从网络加载 JSON5 文件""" + r = httpx.get(url) + if r.status_code != 200: + raise ValueError(f"下载文件失败:{r.text}") + return pyjson5.decode(r.text) + + +def dump_json5(path: Path, data: Any) -> None: + """保存 JSON5 文件 + + 手动添加末尾的逗号和换行符 + """ + data = to_jsonable_python(data) + + content = json.dumps(data, ensure_ascii=False, indent=2) + # 手动添加末尾的逗号和换行符 + # 避免合并时出现冲突 + content = content.replace("}\n]", "},\n]") + content += "\n" + + with open(path, "w", encoding="utf-8") as f: + f.write(content) diff --git a/tests/github/publish/utils/test_publish_resolve_conflict_pull_requests.py b/tests/github/publish/utils/test_publish_resolve_conflict_pull_requests.py index 11079d9a..7907d365 100644 --- a/tests/github/publish/utils/test_publish_resolve_conflict_pull_requests.py +++ b/tests/github/publish/utils/test_publish_resolve_conflict_pull_requests.py @@ -1,6 +1,5 @@ import json from pathlib import Path -from typing import Any import pytest from inline_snapshot import snapshot @@ -8,12 +7,13 @@ from pytest_mock import MockerFixture from respx import MockRouter -from tests.github.utils import MockBody, MockIssue, MockUser, get_github_bot - - -def check_json_data(file: Path, data: Any) -> None: - with open(file, encoding="utf-8") as f: - assert json.load(f) == data +from tests.github.utils import ( + MockBody, + MockIssue, + MockUser, + check_json_data, + get_github_bot, +) @pytest.fixture diff --git a/tests/github/remove/utils/test_update_file.py b/tests/github/remove/utils/test_update_file.py index b7d6bebf..e2cf5ee1 100644 --- a/tests/github/remove/utils/test_update_file.py +++ b/tests/github/remove/utils/test_update_file.py @@ -1,15 +1,11 @@ import json from pathlib import Path -from typing import Any from nonebug import App from pytest_mock import MockerFixture from respx import MockRouter - -def check_json_data(file: Path, data: Any) -> None: - with open(file, encoding="utf-8") as f: - assert json.load(f) == data +from tests.github.utils import check_json_data async def test_update_file( diff --git a/tests/github/utils.py b/tests/github/utils.py index 27ca2daf..5ce4d51b 100644 --- a/tests/github/utils.py +++ b/tests/github/utils.py @@ -3,6 +3,7 @@ from pathlib import Path from typing import Any, Literal +import pyjson5 from githubkit.rest import Issue from pytest_mock import MockFixture @@ -76,8 +77,8 @@ def generate_issue_body_remove( def check_json_data(file: Path, data: Any) -> None: - with open(file) as f: - assert json.load(f) == data + with open(file, encoding="utf-8") as f: + assert pyjson5.decode_io(f) == data # type: ignore @dataclass diff --git a/tests/utils/store_test/conftest.py b/tests/utils/store_test/conftest.py index e9355c20..e6753ad6 100644 --- a/tests/utils/store_test/conftest.py +++ b/tests/utils/store_test/conftest.py @@ -1,6 +1,6 @@ -import json from pathlib import Path +import pyjson5 import pytest from pytest_mock import MockerFixture from respx import MockRouter @@ -20,9 +20,15 @@ def load_json(name: str) -> dict: - path = Path(__file__).parent / "store" / f"{name}.json" + # 商店为 json5 格式 + if name.startswith("store_"): + name = f"{name}.json5" + else: + name = f"{name}.json" + + path = Path(__file__).parent / "store" / name with path.open("r", encoding="utf-8") as f: - return json.load(f) + return pyjson5.decode_io(f) # type: ignore @pytest.fixture diff --git a/tests/utils/store_test/store/store_adapters.json b/tests/utils/store_test/store/store_adapters.json5 similarity index 99% rename from tests/utils/store_test/store/store_adapters.json rename to tests/utils/store_test/store/store_adapters.json5 index ba590be4..b87e68d9 100644 --- a/tests/utils/store_test/store/store_adapters.json +++ b/tests/utils/store_test/store/store_adapters.json5 @@ -23,5 +23,5 @@ "homepage": "https://onebot.adapters.nonebot.dev/", "tags": [], "is_official": true - } + }, ] diff --git a/tests/utils/store_test/store/store_bots.json b/tests/utils/store_test/store/store_bots.json5 similarity index 98% rename from tests/utils/store_test/store/store_bots.json rename to tests/utils/store_test/store/store_bots.json5 index 5e2cb2d3..ffcd9395 100644 --- a/tests/utils/store_test/store/store_bots.json +++ b/tests/utils/store_test/store/store_bots.json5 @@ -19,5 +19,5 @@ "homepage": "https://github.com/cscs181/QQ-GitHub-Bot", "tags": [], "is_official": false - } + }, ] diff --git a/tests/utils/store_test/store/store_drivers.json b/tests/utils/store_test/store/store_drivers.json5 similarity index 99% rename from tests/utils/store_test/store/store_drivers.json rename to tests/utils/store_test/store/store_drivers.json5 index 93bf424f..ebd06919 100644 --- a/tests/utils/store_test/store/store_drivers.json +++ b/tests/utils/store_test/store/store_drivers.json5 @@ -33,5 +33,5 @@ "homepage": "/docs/advanced/driver", "tags": [], "is_official": true - } + }, ] diff --git a/tests/utils/store_test/store/store_plugins.json b/tests/utils/store_test/store/store_plugins.json5 similarity index 99% rename from tests/utils/store_test/store/store_plugins.json rename to tests/utils/store_test/store/store_plugins.json5 index 040b7a4e..dff05ed0 100644 --- a/tests/utils/store_test/store/store_plugins.json +++ b/tests/utils/store_test/store/store_plugins.json5 @@ -25,5 +25,5 @@ "author_id": 1, "tags": [], "is_official": false - } + }, ] diff --git a/tests/utils/store_test/test_utils.py b/tests/utils/store_test/test_utils.py index 819db50e..fa634f7c 100644 --- a/tests/utils/store_test/test_utils.py +++ b/tests/utils/store_test/test_utils.py @@ -6,12 +6,12 @@ async def test_load_json_failed(mocked_api: MockRouter): """测试加载 json 失败""" - from src.providers.store_test.utils import load_json + from src.providers.utils import load_json5_from_web mocked_api.get(STORE_ADAPTERS_URL).respond(404) with pytest.raises(ValueError, match="下载文件失败:"): - load_json(STORE_ADAPTERS_URL) + load_json5_from_web(STORE_ADAPTERS_URL) async def test_get_pypi_data_failed(mocked_api: MockRouter): diff --git a/tests/utils/test_json5.py b/tests/utils/test_json5.py new file mode 100644 index 00000000..d8abd2d7 --- /dev/null +++ b/tests/utils/test_json5.py @@ -0,0 +1,55 @@ +from pathlib import Path + +from inline_snapshot import snapshot + + +async def test_json5_dump(tmp_path: Path) -> None: + """输出 JSON5 + + 有尾随逗号 + """ + from src.providers.utils import dump_json5 + + data = [ + { + "name": "name", + "desc": "desc", + "author": "author", + "homepage": "https://nonebot.dev", + "tags": '[{"label": "test", "color": "#ffffff"}]', + "author_id": 1, + } + ] + + test_file = tmp_path / "test.json5" + dump_json5(test_file, data) + + assert test_file.read_text() == snapshot( + """\ +[ + { + "name": "name", + "desc": "desc", + "author": "author", + "homepage": "https://nonebot.dev", + "tags": "[{\\"label\\": \\"test\\", \\"color\\": \\"#ffffff\\"}]", + "author_id": 1 + }, +] +""" + ) + + +async def test_json5_dump_empty_list(tmp_path: Path) -> None: + """空列表 + + 末尾也应该有换行 + """ + from src.providers.utils import dump_json5 + + data = [] + + test_file = tmp_path / "test.json5" + dump_json5(test_file, data) + + assert test_file.read_text() == snapshot("[]\n") diff --git a/uv.lock b/uv.lock index 368715e7..dd778bc5 100644 --- a/uv.lock +++ b/uv.lock @@ -664,6 +664,7 @@ dependencies = [ { name = "nonebot2" }, { name = "pre-commit" }, { name = "pydantic-extra-types" }, + { name = "pyjson5" }, ] [package.optional-dependencies] @@ -695,6 +696,7 @@ requires-dist = [ { name = "nonebot2", specifier = ">=2.4.0" }, { name = "pre-commit", specifier = ">=4.0.1" }, { name = "pydantic-extra-types", specifier = ">=2.9.0" }, + { name = "pyjson5", specifier = ">=1.6.7" }, { name = "tzdata", marker = "extra == 'plugin'", specifier = ">=2024.2" }, ] @@ -939,6 +941,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/cd/bd196b2cf014afb1009de8b0f05ecd54011d881944e62763f3c1b1e8ef37/pygtrie-2.5.0-py3-none-any.whl", hash = "sha256:8795cda8105493d5ae159a5bef313ff13156c5d4d72feddefacaad59f8c8ce16", size = 25099 }, ] +[[package]] +name = "pyjson5" +version = "1.6.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/7a/c5851cac7688edb44f3d40349e1cdaa491d9ce503d4510dced7ac6dad36c/pyjson5-1.6.7.tar.gz", hash = "sha256:c1158a27c72c99eb3caa817c70a6218028e7f48fca1736db6d05852b1586ce42", size = 298365 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/f4/a21a15a4a0bb659fa7ec308c3efe4d3c0f590d8576e7dcaa3899cd62476c/pyjson5-1.6.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:48d553948b86f0a7287c901fa4feab34d29f112d3b48da888b37c9e58614ecd3", size = 326207 }, + { url = "https://files.pythonhosted.org/packages/41/40/d88b1860af64f554c94cb9323bcb1c58c35fa6e2141e780dc72f96365aa0/pyjson5-1.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:698f4bbbcba1cd8aec5fbbc920245d4864ac2203ba6d3a50fcd7f5e0ecdefc6e", size = 172862 }, + { url = "https://files.pythonhosted.org/packages/bb/1b/ee26d338a5a303f5a644f470e58811705e60f6015cef808bc60be049bd36/pyjson5-1.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e2277d4dbb98021e78f6ba9f63727c0c41b84d2f2c118f1bfb086954aadd6ae2", size = 161823 }, + { url = "https://files.pythonhosted.org/packages/0f/1a/f895fc9a6f84977712419cc4e79e417005f035b0881a9705157fb1eb4a08/pyjson5-1.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c061648c96c619d3666e3433798c3359df3fe8fbcc84736849eabe65351657f8", size = 173612 }, + { url = "https://files.pythonhosted.org/packages/f5/41/e81664ecd015336504bbe10c37b33f560091bce8cee0083a523493c351cf/pyjson5-1.6.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d74c70c4527b7866d413ed74dbbcc29f3623d09411c1287d5f90aabe93a7b481", size = 194798 }, + { url = "https://files.pythonhosted.org/packages/e2/b1/9973d34d78fcef6575e537f2d1bbb573c0d3b632f5216ced1b8ab87bbb40/pyjson5-1.6.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f660cd704f0ace0c34804ff2a3a1a3ed42a4a0900a44701c234e56ebe8a99485", size = 174690 }, + { url = "https://files.pythonhosted.org/packages/c4/81/ed2f77052bc5c9a3889a9648c035bfda0f3aeddb4d72fcdab054706f7c2a/pyjson5-1.6.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d2dbb80fd6e37530dd15a6aa9397d23502a6ec16754e535d132b92bbc2373c", size = 184476 }, + { url = "https://files.pythonhosted.org/packages/2f/c0/64b86c373bf61b7a4ae87ab9457a8711165faf4789442e000cd2d82fdb56/pyjson5-1.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37dc8a731720321d07f248f79484cc3c72aabaed439b7326397f052fffabce3a", size = 195694 }, + { url = "https://files.pythonhosted.org/packages/44/8d/fe3d666ab1c405c4124b419f6ce47fb2cb2abf04674de8a57c44fd2dd633/pyjson5-1.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3ffeb1c61ce74675c5f476eba3b6f05922bbf2e258e6b8101457ff2c5ead1f43", size = 1119525 }, + { url = "https://files.pythonhosted.org/packages/03/58/9d1dae62d3cf1fb7c356c3b759dd9f702f514f5c233c2dbbdab4c200ac82/pyjson5-1.6.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e5630927c3b70c251a4640a2677f18b39c52a5a6362557a153c4b9de45d09818", size = 997533 }, + { url = "https://files.pythonhosted.org/packages/ee/74/9a44bd1bc9e9af3ed13c218ced25cb4526e7ada0b651d01d9a5e8012557e/pyjson5-1.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7de9c2f9bba7955bc07fb1fd5f495e02825c96857c4503191b85bba7a9d66209", size = 1276428 }, + { url = "https://files.pythonhosted.org/packages/9e/9f/b25a4d5528c8d9164afee038b631429a879c42cef68e0789b5981920fef5/pyjson5-1.6.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7678bc99a1e7f8b0100fb9c8d734ed910dab554f58ea4549018dfb6a74e54587", size = 1228859 }, + { url = "https://files.pythonhosted.org/packages/14/b0/f8b0b69648c6133bff7baf7cfee5fe37bf29c99580483a5e81d9f8895563/pyjson5-1.6.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:6a347bb4cd476af307abe15005e8d2c554928860bec257cb10c74322b31b6c52", size = 1318323 }, + { url = "https://files.pythonhosted.org/packages/f7/e3/d1c1e331e3c5b0d10f14a878719e6c5a73b24e39f0ff901eda8921d3f47d/pyjson5-1.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2009ebb4a32c1db8cff54b1faa50cc70078e448c9ee0522d5634173a8bdb167", size = 1177252 }, + { url = "https://files.pythonhosted.org/packages/a7/e7/beb42b85a743b4f309b2cabd802e6c287284a0472f3f46bb9136f0ebccaf/pyjson5-1.6.7-cp312-cp312-win32.whl", hash = "sha256:ae2cc1b29821773caf6dd35b4bf86d093ea14474e7a23e442c9c692742709d8e", size = 126625 }, + { url = "https://files.pythonhosted.org/packages/7b/f8/642996d082317d5327a65bf47b08618062eca053329f6eed3789a276aa7f/pyjson5-1.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:75fdec45b6a06ffdea1a6c68b475437018a53ba5add34c1b0f0372c44092d2ef", size = 143014 }, + { url = "https://files.pythonhosted.org/packages/00/7b/8405b7da087b1a83c6e358ab9f60ee2b553049130d4c723deaf2d8510ab8/pyjson5-1.6.7-cp312-cp312-win_arm64.whl", hash = "sha256:623586900a013ae3f6b2c134b1867e914a78e6a20362b89597ae8650736c1171", size = 124367 }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e7ba20b974da01164650df22a679dbf3cfc9cbed4f5cbfcdd0ac79d5d82b/pyjson5-1.6.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a1b9972751135ffefce3dac34b3f7d3f7858ff9db5baea71722b6ef39bedcdac", size = 324267 }, + { url = "https://files.pythonhosted.org/packages/c1/85/43b7af6337cc7cca9f841f48660bfc2e239b2f4a421da8a574faf8a13531/pyjson5-1.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:174980dceacb718984620c12f0740ab0015a0e89593fc77690dc32cd1361c0bd", size = 171835 }, + { url = "https://files.pythonhosted.org/packages/ef/70/99100b776104d6821f12dbe8b92ad0404f672757cf90b2f498447c71034e/pyjson5-1.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff35c3e4b069c88a4fc7920d75a5021973b734363bc3c6dea1114e98f5d0acf", size = 161015 }, + { url = "https://files.pythonhosted.org/packages/ce/ba/71da8d07c47228c43638cc27f781156487bf75e5f9b920e2de37fe60326c/pyjson5-1.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7362f068368e7da2b9106f976d7a4f8cf72f01981f755c3e02558e7dc447b590", size = 172880 }, + { url = "https://files.pythonhosted.org/packages/fb/2f/afe3cfb5d9c875153f84830dec761c5049fa688c9a62abce5ac4cee50653/pyjson5-1.6.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbc1e984a83d80a1589fe01d6599f56f269670ef1c38257cd54c56e986ccc877", size = 192513 }, + { url = "https://files.pythonhosted.org/packages/0f/12/b00d4a705b8b537dcc4367ddbaf63060dcc07d9ec8635d307a475ceaca0b/pyjson5-1.6.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:60534af7f7894f238b459f9a1a24d84cd04d1f722cecccee75f7c5a44e7e6ec0", size = 173657 }, + { url = "https://files.pythonhosted.org/packages/1f/b6/c326f9e52677946a7d43066792a9c4f0c8ee08f4626bca3e0ddbe1999240/pyjson5-1.6.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20c922b4186662df00d67662c85856f86635c023aac9d974130ad875d5d2218b", size = 183336 }, + { url = "https://files.pythonhosted.org/packages/a3/c9/9ad56314ddc22c67b69bf6dd6e51c58b07698d580d0f4bd927c072debcad/pyjson5-1.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b55f268032463b8d97c3ef37575cb99b707405e337ba8d4ab770ddca35fd47d", size = 194500 }, + { url = "https://files.pythonhosted.org/packages/6a/4f/8ddafbdc94bc99bbd477dab612b96bdd8b0de3c787da4e356cedf88e1473/pyjson5-1.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d5f7bafe95acb3a12bdc776804c9ed265fde0bfbece64341ab21118e3ec4d96c", size = 1122138 }, + { url = "https://files.pythonhosted.org/packages/a8/65/e2757789bfa04c54d7dc873c35586c5e5471cc5a8f0b42e7476148d94abb/pyjson5-1.6.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:12ff7a68cea25bef100592b837b0c002e8cbc80bb0e3f1a351dbed52fc4670a0", size = 996045 }, + { url = "https://files.pythonhosted.org/packages/ea/02/a83d94a38fa004729819017d3f312399a405b938ab6eef06c3cef4d51440/pyjson5-1.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1cffc3454b31dab677bd405b44bd90da2016e506d041e305670565afd041d936", size = 1278541 }, + { url = "https://files.pythonhosted.org/packages/bf/55/9ed97c7ffc765e1ed3dd3848854f63348832cd622add07fe6689161e7944/pyjson5-1.6.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5102731b070f1b6157f0c2cec2ecf3bf6278044f9add5970e9186c846ab9fc68", size = 1229362 }, + { url = "https://files.pythonhosted.org/packages/5d/40/260a43613f94452a4b046a07126deb64474d6a29223c70b1c437ca22731d/pyjson5-1.6.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0923adc12795a6d35f7163a45da15bee36134e73406b9481d632bcb750aa598b", size = 1318701 }, + { url = "https://files.pythonhosted.org/packages/74/29/f134caf66fcd6fab73438448f0ba54165ea629cef811519fdc1251439c77/pyjson5-1.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bddf838024dcfc2d84634293be740489f7b32226a4fcc60dfe00b2897b00611c", size = 1180567 }, + { url = "https://files.pythonhosted.org/packages/68/a7/017a5ead40cb2c6d317f8f5b8eb5da4e8386ee7f42f56768aee9ff80ab7a/pyjson5-1.6.7-cp313-cp313-win32.whl", hash = "sha256:337dbc0adc037c4b8f807a32f40bc055d53a5c33af95d04ab67c2cb1c1150a8c", size = 126403 }, + { url = "https://files.pythonhosted.org/packages/8b/b1/4eccd027788100fa9fc02247bcb7fa090451710ef9c18f27b84314665c4f/pyjson5-1.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:2f4ebe8e379fb47b19d99d7e6beb2df4de09d4b48331bf438b5cd6164c64dd22", size = 142645 }, + { url = "https://files.pythonhosted.org/packages/3b/8f/e17c59e0e333c9af3d9d5478dfe069bbb2d0cb22670de05302c9995c74e4/pyjson5-1.6.7-cp313-cp313-win_arm64.whl", hash = "sha256:d83382da419f0dfede6f0bcf3648317ae76efa546bb4cad620d4bfab6c6c6ffa", size = 124146 }, +] + [[package]] name = "pyjwt" version = "2.9.0"