qqmusic-api-python 0.7.0__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/workflows/docs.yml +6 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/workflows/release.yml +6 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/workflows/testing.yml +2 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/PKG-INFO +1 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/coding.md +77 -64
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/contributing.md +9 -1
- qqmusic_api_python-0.7.1/docs/reference/model/helper.md +3 -0
- qqmusic_api_python-0.7.1/docs/reference/model/private_message.md +3 -0
- qqmusic_api_python-0.7.1/docs/reference/modules/helper.md +3 -0
- qqmusic_api_python-0.7.1/docs/reference/modules/helper_utils.md +3 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/release-notes.md +16 -0
- qqmusic_api_python-0.7.1/docs/tutorial/pagination.md +177 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/prek.toml +5 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/pyproject.toml +0 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/__init__.py +1 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/core/client.py +15 -53
- qqmusic_api_python-0.7.1/qqmusic_api/core/pagination.py +464 -0
- qqmusic_api_python-0.7.1/qqmusic_api/core/request.py +220 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/comment.py +18 -0
- qqmusic_api_python-0.7.1/qqmusic_api/models/lyric.py +136 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/request.py +0 -30
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/search.py +19 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/_base.py +34 -160
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/album.py +13 -9
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/comment.py +101 -62
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/helper.py +0 -6
- qqmusic_api_python-0.7.1/qqmusic_api/modules/lyric.py +131 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/mv.py +7 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/private_message.py +17 -17
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/recommend.py +23 -37
- qqmusic_api_python-0.7.1/qqmusic_api/modules/search.py +289 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/singer.py +36 -35
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/song.py +24 -19
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/songlist.py +8 -9
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/top.py +7 -8
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/user.py +66 -69
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/utils/mqtt.py +4 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/conftest.py +0 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_album.py +4 -4
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_comment.py +27 -13
- qqmusic_api_python-0.7.1/tests/test_lyric.py +70 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_mv.py +4 -4
- qqmusic_api_python-0.7.1/tests/test_pagination.py +401 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_recommend.py +1 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_search.py +25 -14
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_song.py +10 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/uv.lock +444 -535
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/app.py +14 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/auth.py +6 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/cache.py +13 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/security.py +9 -3
- qqmusic_api_python-0.7.1/web/src/modules/__init__.py +12 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/modules/comment.py +5 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/modules/login.py +9 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/modules/mv.py +3 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/modules/singer.py +2 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/modules/song.py +12 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/modules/songlist.py +3 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/_helpers.py +10 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/comment.py +8 -9
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/login.py +1 -13
- qqmusic_api_python-0.7.1/web/src/routes/lyric.py +48 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/mv.py +0 -3
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/recommend.py +1 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/singer.py +0 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/song.py +8 -18
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/songlist.py +0 -3
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/user.py +6 -6
- qqmusic_api_python-0.7.1/web/src/routing/adapter_registry.py +56 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routing/docstrings.py +7 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routing/executor.py +9 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routing/params.py +32 -11
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routing/route_types.py +3 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routing/router_factory.py +42 -8
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/tests/test_web_core.py +3 -3
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/tests/test_web_docstrings.py +20 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/tests/test_web_routes.py +7 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/zensical.toml +7 -1
- qqmusic_api_python-0.7.0/.agents/skills/tarsio/SKILL.md +0 -247
- qqmusic_api_python-0.7.0/.agents/skills/tarsio/references/api-reference.md +0 -246
- qqmusic_api_python-0.7.0/docs/tutorial/pagination.md +0 -112
- qqmusic_api_python-0.7.0/qqmusic_api/core/pagination.py +0 -499
- qqmusic_api_python-0.7.0/qqmusic_api/core/request.py +0 -150
- qqmusic_api_python-0.7.0/qqmusic_api/models/lyric.py +0 -34
- qqmusic_api_python-0.7.0/qqmusic_api/modules/lyric.py +0 -53
- qqmusic_api_python-0.7.0/qqmusic_api/modules/search.py +0 -181
- qqmusic_api_python-0.7.0/tests/test_lyric.py +0 -39
- qqmusic_api_python-0.7.0/web/src/modules/__init__.py +0 -1
- qqmusic_api_python-0.7.0/web/src/routes/lyric.py +0 -10
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.agents/skills/pydantic/SKILL.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.agents/skills/python-standards/SKILL.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.agents/skills/uv-package-manager/SKILL.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.dockerignore +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/renovate.json +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.github/workflows/checking.yaml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.gitignore +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/.markdownlint-cli2.yaml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/AGENTS.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/CLAUDE.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/LICENSE +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/README.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/assets/qq-music.svg +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/cliff.toml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/index.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/core/client.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/core/exception.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/core/pagination.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/core/request.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/core/versioning.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/album.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/base.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/comment.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/login.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/lyric.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/mv.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/recommend.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/request.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/search.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/singer.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/song.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/songlist.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/top.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/model/user.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/album.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/comment.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/login.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/login_utils.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/lyric.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/mv.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/private_message.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/recommend.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/search.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/singer.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/song.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/songlist.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/top.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/reference/modules/user.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/tutorial/client.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/tutorial/credential.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/tutorial/download.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/tutorial/error-handling.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/tutorial/login.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/tutorial/start.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/docs/tutorial/web.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/examples/download_song.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/examples/get_all_sheets.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/examples/phone_login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/examples/private_message.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/examples/qrcode_login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/examples/upload_file.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/algorithms/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/algorithms/sign.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/algorithms/tripledes.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/core/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/core/exceptions.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/core/versioning.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/_validator.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/album.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/base.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/helper.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/mv.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/private_message.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/recommend.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/singer.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/song.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/songlist.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/top.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/models/user.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/helper_utils.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/modules/login_utils.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/scripts/ag-1.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_login_utils.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_private_message.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/tests/test_user.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/.gitignore +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/Dockerfile +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/README.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/accounts.example.toml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/config.example.toml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/docker-compose.example.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/requirements.txt +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/run.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/config.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/credential_store.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/deps.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/core/response.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/album.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/search.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routes/top.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/src/routing/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/tests/conftest.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/tests/test_web_enums.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.1}/web/tests/test_web_route_validation.py +0 -0
|
@@ -19,22 +19,22 @@ jobs:
|
|
|
19
19
|
runs-on: ubuntu-latest
|
|
20
20
|
steps:
|
|
21
21
|
- name: Checkout
|
|
22
|
-
uses: actions/checkout@v6
|
|
22
|
+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
23
23
|
with:
|
|
24
24
|
fetch-depth: 0
|
|
25
25
|
token: ${{ secrets.GH_TOKEN }}
|
|
26
26
|
- name: Setup Pages
|
|
27
27
|
id: pages
|
|
28
|
-
uses: actions/configure-pages@v6
|
|
28
|
+
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6
|
|
29
29
|
- name: Install uv
|
|
30
|
-
uses: astral-sh/setup-uv@v7
|
|
30
|
+
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
|
|
31
31
|
with:
|
|
32
32
|
enable-cache: true
|
|
33
33
|
python-version: 3.12
|
|
34
34
|
- name: Install the project
|
|
35
35
|
run: uv sync --group docs
|
|
36
36
|
- name: Generate release-notes
|
|
37
|
-
uses: orhun/git-cliff-action@v4
|
|
37
|
+
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4
|
|
38
38
|
with:
|
|
39
39
|
config: cliff.toml
|
|
40
40
|
args: --verbose --strip all
|
|
@@ -55,7 +55,7 @@ jobs:
|
|
|
55
55
|
- name: Build with zensical
|
|
56
56
|
run: uv run zensical build
|
|
57
57
|
- name: Upload artifact
|
|
58
|
-
uses: actions/upload-pages-artifact@v5
|
|
58
|
+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
|
|
59
59
|
with:
|
|
60
60
|
path: ./site
|
|
61
61
|
|
|
@@ -68,4 +68,4 @@ jobs:
|
|
|
68
68
|
steps:
|
|
69
69
|
- name: Deploy to GitHub Pages
|
|
70
70
|
id: deployment
|
|
71
|
-
uses: actions/deploy-pages@v5
|
|
71
|
+
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
|
|
@@ -15,13 +15,13 @@ jobs:
|
|
|
15
15
|
outputs:
|
|
16
16
|
release_body: ${{ steps.git-cliff.outputs.content }}
|
|
17
17
|
steps:
|
|
18
|
-
- uses: actions/checkout@v6
|
|
18
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
19
19
|
with:
|
|
20
20
|
fetch-depth: 0
|
|
21
21
|
token: ${{ secrets.GH_TOKEN }}
|
|
22
22
|
- name: Generate release body
|
|
23
23
|
id: git-cliff
|
|
24
|
-
uses: orhun/git-cliff-action@v4
|
|
24
|
+
uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4
|
|
25
25
|
with:
|
|
26
26
|
args: -vv --latest --strip all
|
|
27
27
|
|
|
@@ -32,11 +32,11 @@ jobs:
|
|
|
32
32
|
permissions:
|
|
33
33
|
id-token: write
|
|
34
34
|
steps:
|
|
35
|
-
- uses: actions/checkout@v6
|
|
35
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
36
36
|
with:
|
|
37
37
|
token: ${{ secrets.GH_TOKEN }}
|
|
38
38
|
- name: Install uv
|
|
39
|
-
uses: astral-sh/setup-uv@v7
|
|
39
|
+
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
|
|
40
40
|
with:
|
|
41
41
|
enable-cache: true
|
|
42
42
|
- name: Install the project
|
|
@@ -51,11 +51,11 @@ jobs:
|
|
|
51
51
|
needs: [generate-release-body, publish]
|
|
52
52
|
steps:
|
|
53
53
|
- name: Checkout
|
|
54
|
-
uses: actions/checkout@v6
|
|
54
|
+
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
55
55
|
with:
|
|
56
56
|
token: ${{ secrets.GH_TOKEN }}
|
|
57
57
|
- name: Release
|
|
58
|
-
uses: softprops/action-gh-release@v3
|
|
58
|
+
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3
|
|
59
59
|
with:
|
|
60
60
|
body: ${{ needs.generate-release-body.outputs.release_body }}
|
|
61
61
|
tag_name: ${{ github.ref_name }}
|
|
@@ -10,9 +10,9 @@ jobs:
|
|
|
10
10
|
test:
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
|
-
- uses: actions/checkout@v6
|
|
13
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
|
14
14
|
- name: Install uv
|
|
15
|
-
uses: astral-sh/setup-uv@v7
|
|
15
|
+
uses: astral-sh/setup-uv@94527f2e458b27549849d47d273a16bec83a01e9 # v7
|
|
16
16
|
with:
|
|
17
17
|
enable-cache: true
|
|
18
18
|
python-version: "3.10"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qqmusic-api-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: QQ音乐API封装库
|
|
5
5
|
Project-URL: documentation, https://l-1124.github.io/QQMusicApi/
|
|
6
6
|
Project-URL: homepage, https://l-1124.github.io/QQMusicApi/
|
|
@@ -35,7 +35,6 @@ Requires-Dist: niquests[speedups]>=3.18.7
|
|
|
35
35
|
Requires-Dist: orjson>=3.10.15
|
|
36
36
|
Requires-Dist: paho-mqtt>=2.1.0
|
|
37
37
|
Requires-Dist: pydantic>=2.13.3
|
|
38
|
-
Requires-Dist: tarsio>=0.5.1
|
|
39
38
|
Requires-Dist: typing-extensions>=4.12.2
|
|
40
39
|
Provides-Extra: upload
|
|
41
40
|
Requires-Dist: cos-python-sdk-v5>=1.9.44; extra == 'upload'
|
|
@@ -15,9 +15,9 @@
|
|
|
15
15
|
-> Request
|
|
16
16
|
-> await request
|
|
17
17
|
-> Client.execute(request)
|
|
18
|
-
-> Client.request_api(...)
|
|
18
|
+
-> Client.request_api(...)
|
|
19
19
|
-> Client._build_result(...)
|
|
20
|
-
-> 返回原始 dict
|
|
20
|
+
-> 返回原始 dict 或 Pydantic 模型
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
### 批量并发请求
|
|
@@ -66,6 +66,7 @@ class FooApi(ApiModule):
|
|
|
66
66
|
# qqmusic_api/core/client.py
|
|
67
67
|
from functools import cached_property
|
|
68
68
|
|
|
69
|
+
|
|
69
70
|
class Client:
|
|
70
71
|
@cached_property
|
|
71
72
|
def foo(self) -> "FooApi":
|
|
@@ -81,9 +82,9 @@ API 方法返回 `Request` 对象,不直接发起请求。使用 `self._build_
|
|
|
81
82
|
def get_detail(self, song_id: int):
|
|
82
83
|
"""获取歌曲详情."""
|
|
83
84
|
return self._build_request(
|
|
84
|
-
module="music.songDetail",
|
|
85
|
-
method="GetDetail",
|
|
86
|
-
param={"songid": song_id},
|
|
85
|
+
module="music.songDetail", # 接口所属模块
|
|
86
|
+
method="GetDetail", # 方法名
|
|
87
|
+
param={"songid": song_id}, # 业务参数
|
|
87
88
|
)
|
|
88
89
|
```
|
|
89
90
|
|
|
@@ -103,21 +104,20 @@ async def quick_search(self, keyword: str) -> dict[str, Any]:
|
|
|
103
104
|
|
|
104
105
|
### `_build_request` 参数说明
|
|
105
106
|
|
|
106
|
-
| 参数
|
|
107
|
-
|
|
108
|
-
| `module`
|
|
109
|
-
| `method`
|
|
110
|
-
| `param`
|
|
111
|
-
| `response_model`
|
|
112
|
-
| `comm`
|
|
113
|
-
| `override_comm`
|
|
114
|
-
| `credential`
|
|
115
|
-
| `platform`
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `refresh_meta` | `RefreshMeta` 或 `None` | 换一批元数据,提供后返回 `RefreshableRequest` |
|
|
107
|
+
| 参数 | 类型 | 说明 |
|
|
108
|
+
|--------------------|-------------------------------|-------------------------------------------------------------------------------------------------------------|
|
|
109
|
+
| `module` | `str` | 接口所属模块名 |
|
|
110
|
+
| `method` | `str` | 方法名 |
|
|
111
|
+
| `param` | `dict` | 业务参数 |
|
|
112
|
+
| `response_model` | `type[BaseModel]` 或 `None` | 响应模型,为 None 时返回原始 dict |
|
|
113
|
+
| `comm` | `dict` 或 `None` | 附加的公共参数 |
|
|
114
|
+
| `override_comm` | `bool` | 为 True 时 `comm` 完全替代自动生成的参数;为 False 时合并 |
|
|
115
|
+
| `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证 |
|
|
116
|
+
| `platform` | `Platform` 或 `None` | 覆盖本次请求的平台 |
|
|
117
|
+
| `preserve_bool` | `bool` | 是否保留布尔值原样(默认转为 0/1 整型) |
|
|
118
|
+
| `sign` | `bool` | 是否对请求进行签名 |
|
|
119
|
+
| `pager_strategy` | `PagerStrategy` 或 `None` | 分页策略,提供后返回 `PaginatedRequest`;可链式调用 `.with_extractor()` 提升为 `ItemPaginatedRequest` |
|
|
120
|
+
| `refresh_strategy` | `RefresherStrategy` 或 `None` | 换一批策略,提供后返回 `RefreshableRequest`;可链式调用 `.with_extractor()` 提升为 `ItemRefreshableRequest` |
|
|
121
121
|
|
|
122
122
|
### `client.request` 参数说明
|
|
123
123
|
|
|
@@ -176,6 +176,17 @@ class MyResponse(Response):
|
|
|
176
176
|
|
|
177
177
|
`Response` 基类配置了 `frozen=True`(不可变)和 `extra="ignore"`(忽略多余字段)。
|
|
178
178
|
|
|
179
|
+
!!! warning "Pydantic 默认值规范"
|
|
180
|
+
|
|
181
|
+
定义模型时应避免使用 `None` 作为隐式兜底默认值。如果字段可选或为空,应当使用显式的空标量,或通过 `Field(default_factory=...)` 声明:
|
|
182
|
+
```python
|
|
183
|
+
class Album(Response):
|
|
184
|
+
name: str = ""
|
|
185
|
+
publish_time: str = ""
|
|
186
|
+
# 列表必须使用 default_factory
|
|
187
|
+
singers: list[Singer] = Field(default_factory=list)
|
|
188
|
+
```
|
|
189
|
+
|
|
179
190
|
### JSONPath 字段映射
|
|
180
191
|
|
|
181
192
|
可以通过 `Field(json_schema_extra={"jsonpath": ...})` 声明字段的 JSONPath 映射路径,自动从嵌套响应中提取数据:
|
|
@@ -239,14 +250,16 @@ def get_vip_info(self, *, credential: Credential | None = None):
|
|
|
239
250
|
> 若接口需要凭证对象的字段来构建请求参数,
|
|
240
251
|
> 仍可显式调用 `_require_login` 获取凭证对象。
|
|
241
252
|
|
|
242
|
-
##
|
|
253
|
+
## 连续翻页与批次刷新
|
|
243
254
|
|
|
244
255
|
### 连续翻页
|
|
245
256
|
|
|
246
|
-
通过 `
|
|
257
|
+
通过 `pager_strategy` 声明连续翻页能力,建议配合显示 Generic 标注(形如
|
|
258
|
+
`OffsetStrategy[GetSonglistDetailResponse]`)以确保静态类型检查与类型推断的准确性,并通过 `.with_extractor()`
|
|
259
|
+
链式调用绑定实体数据项的提取逻辑:
|
|
247
260
|
|
|
248
261
|
```python
|
|
249
|
-
from ..core.pagination import OffsetStrategy
|
|
262
|
+
from ..core.pagination import OffsetStrategy
|
|
250
263
|
|
|
251
264
|
|
|
252
265
|
def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
|
|
@@ -260,23 +273,23 @@ def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
|
|
|
260
273
|
"song_num": num,
|
|
261
274
|
},
|
|
262
275
|
response_model=GetSonglistDetailResponse,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
),
|
|
276
|
+
pager_strategy=OffsetStrategy[GetSonglistDetailResponse](
|
|
277
|
+
offset_key="song_begin",
|
|
278
|
+
page_size_key="song_num",
|
|
279
|
+
has_more_extractor=lambda response: bool(response.hasmore),
|
|
280
|
+
total_extractor=lambda response: response.total,
|
|
281
|
+
count_extractor=lambda response: len(response.songs),
|
|
270
282
|
),
|
|
271
|
-
)
|
|
283
|
+
).with_extractor(lambda response: response.songs)
|
|
272
284
|
```
|
|
273
285
|
|
|
274
|
-
###
|
|
286
|
+
### 批次刷新 (Batch Refresh)
|
|
275
287
|
|
|
276
|
-
|
|
288
|
+
批次刷新(Batch Refresh)是一种针对推荐或关联接口、支持游标复位与防循环重复游标的特殊游标分页,同样通过 `pager_strategy` 声明:
|
|
277
289
|
|
|
278
290
|
```python
|
|
279
|
-
from ..core.pagination import BatchRefreshStrategy
|
|
291
|
+
from ..core.pagination import BatchRefreshStrategy
|
|
292
|
+
from ..models.base import MV
|
|
280
293
|
|
|
281
294
|
|
|
282
295
|
def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
@@ -286,14 +299,12 @@ def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
|
286
299
|
method="GetSongRelatedMv",
|
|
287
300
|
param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
|
|
288
301
|
response_model=GetRelatedMvResponse,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
cursor=lambda response: response.mv[-1].id if response.mv else None,
|
|
294
|
-
),
|
|
302
|
+
pager_strategy=BatchRefreshStrategy[GetRelatedMvResponse](
|
|
303
|
+
refresh_key="lastmvid",
|
|
304
|
+
cursor_extractor=lambda response: response.mv[-1].id if response.mv else None,
|
|
305
|
+
has_more_extractor=lambda response: bool(response.has_more),
|
|
295
306
|
),
|
|
296
|
-
)
|
|
307
|
+
).with_extractor(lambda response: response.mv)
|
|
297
308
|
```
|
|
298
309
|
|
|
299
310
|
### 内置策略速查
|
|
@@ -304,26 +315,7 @@ def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
|
304
315
|
| `OffsetStrategy` | 偏移量滑窗 | `offset_key` + `page_size_key` |
|
|
305
316
|
| `CursorStrategy` | 响应游标回写 | `cursor_key` |
|
|
306
317
|
| `MultiFieldContinuationStrategy` | 多字段续翻 | 自定义 `build_next_params` 函数 |
|
|
307
|
-
| `BatchRefreshStrategy` |
|
|
308
|
-
|
|
309
|
-
`pager_meta` 与 `refresh_meta` 不能同时声明。
|
|
310
|
-
|
|
311
|
-
## JCE (Tars) 协议
|
|
312
|
-
|
|
313
|
-
部分接口使用 JCE 二进制协议而非 JSON。通过 `is_jce=True` 启用:
|
|
314
|
-
|
|
315
|
-
```python
|
|
316
|
-
def get_something(self):
|
|
317
|
-
"""获取数据 (JCE 协议)."""
|
|
318
|
-
return self._build_request(
|
|
319
|
-
module="music.foo.Svc",
|
|
320
|
-
method="GetSomething",
|
|
321
|
-
param={0: "value"}, # JCE 使用整数 key
|
|
322
|
-
is_jce=True,
|
|
323
|
-
)
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
JCE 协议的响应会自动解码为 `TarsDict`。
|
|
318
|
+
| `BatchRefreshStrategy` | 批次刷新 | `refresh_key` |
|
|
327
319
|
|
|
328
320
|
## 请求签名
|
|
329
321
|
|
|
@@ -372,6 +364,20 @@ self._build_request(
|
|
|
372
364
|
)
|
|
373
365
|
```
|
|
374
366
|
|
|
367
|
+
## 异常处理
|
|
368
|
+
|
|
369
|
+
在抛出或处理异常时,应使用项目统一的基于领域驱动(DDD)风格的异常类(继承自 `BaseApiException` 或 `ApiException`
|
|
370
|
+
)。在包装底层异常时,必须使用原生异常链(`raise ... from exc`)保留堆栈追踪:
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
from ..core.exceptions import ApiDataError
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
# ...
|
|
377
|
+
except KeyError as e:
|
|
378
|
+
raise ApiDataError("无法解析歌曲信息") from e
|
|
379
|
+
```
|
|
380
|
+
|
|
375
381
|
## 编写测试
|
|
376
382
|
|
|
377
383
|
测试文件放在 `tests/` 下,按模块命名(如 `test_song.py`)。
|
|
@@ -398,8 +404,15 @@ async def test_query_song(client: Client) -> None:
|
|
|
398
404
|
```python
|
|
399
405
|
@pytest.mark.parametrize("page", [1, 2])
|
|
400
406
|
async def test_general_search(client: Client, page: int) -> None:
|
|
401
|
-
"""
|
|
402
|
-
|
|
407
|
+
"""测试综合搜索翻页逻辑."""
|
|
408
|
+
try:
|
|
409
|
+
result = await client.search.general_search("周杰伦", page=page)
|
|
410
|
+
except Exception as e:
|
|
411
|
+
# 示例:优雅处理网络风控或限流 (需根据实际异常类型调整)
|
|
412
|
+
if "limit" in str(e).lower() or "risk" in str(e).lower():
|
|
413
|
+
pytest.skip(f"Triggered rate limit or risk control: {e}")
|
|
414
|
+
raise
|
|
415
|
+
|
|
403
416
|
assert result.song.items is not None
|
|
404
417
|
```
|
|
405
418
|
|
|
@@ -419,7 +432,7 @@ async def test_get_vip_info(authenticated_client: Client) -> None:
|
|
|
419
432
|
```python
|
|
420
433
|
async def test_search_paginate(client: Client) -> None:
|
|
421
434
|
"""测试搜索分页."""
|
|
422
|
-
pager = client.search.search_by_type("周杰伦", num=5).
|
|
435
|
+
pager = client.search.search_by_type("周杰伦", num=5).pager(limit=2)
|
|
423
436
|
|
|
424
437
|
assert pager.has_more() is True
|
|
425
438
|
first_page = await pager.next()
|
|
@@ -76,11 +76,19 @@ uv run mkdocs serve
|
|
|
76
76
|
* Lint 使用 [Ruff](https://docs.astral.sh/ruff/)
|
|
77
77
|
* 类型检查使用 `pyrefly`
|
|
78
78
|
|
|
79
|
+
## 测试规范
|
|
80
|
+
|
|
81
|
+
* **专注 Modules 层**:测试重心放在 `modules` 层,将其视为黑盒。采用基于数据驱动(`@pytest.mark.parametrize`)的函数式测试方法。
|
|
82
|
+
* **真实网络请求(No Mock)**:禁止 Mock 底层网络请求或核心组装逻辑。必须直接与真实的 QQ 音乐 API 交互。
|
|
83
|
+
* **优雅处理限流**:触发上游 API 风控或频率限制异常时,必须捕获异常并调用 `pytest.skip()` 安全跳过,严禁导致测试失败。
|
|
84
|
+
* **平铺函数写法**:摒弃测试类(`class TestXXX`),强制采用独立纯函数(Flat Functions)。
|
|
85
|
+
* **无需过度清理**:对于获取、查询类操作,验证连通性与模型解析即可。
|
|
86
|
+
|
|
79
87
|
## 代码注释
|
|
80
88
|
|
|
81
89
|
* 注释内容包括:模块注释、类注释、函数注释、参数类型注释、返回值注释
|
|
82
90
|
* 注释风格遵循 [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
|
83
|
-
*
|
|
91
|
+
* 测试用例必须包含**单行中文 docstring**,且内部必须使用**英文标点符号**。
|
|
84
92
|
|
|
85
93
|
## 文档规范
|
|
86
94
|
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
|
|
2
|
+
## [[0.7.0](https://github.com/L-1124/QQMusicApi/compare/v0.6.9..v0.7.0)] - 2026-07-22
|
|
3
|
+
|
|
4
|
+
### Bug 修复
|
|
5
|
+
|
|
6
|
+
* fix retry import path ([75d0693](https://github.com/L-1124/QQMusicApi/commit/75d069344a07c6713b4a616ed6304ca6dc1b740a)) by [@L-1124](https://github.com/L-1124)
|
|
7
|
+
|
|
8
|
+
### 功能更新
|
|
9
|
+
|
|
10
|
+
* **(lyric)** 支持指定特殊歌曲类型(song_type)查询歌词 ([1b0aae0](https://github.com/L-1124/QQMusicApi/commit/1b0aae0db3ee6876b3a77b8d1ce3057b4b3c9cd5)) by [@L-1124](https://github.com/L-1124)
|
|
11
|
+
* **(song)** [**breaking**] 重构歌曲查询支持 SongQueryInfo 与 Web POST 批量路由 ([4cbb2c6](https://github.com/L-1124/QQMusicApi/commit/4cbb2c6e993d58db71494ce31809a144bfc33be4)) by [@L-1124](https://github.com/L-1124)
|
|
12
|
+
|
|
13
|
+
### 贡献者
|
|
14
|
+
|
|
15
|
+
* @L-1124
|
|
16
|
+
* @github-actions[bot]
|
|
17
|
+
|
|
2
18
|
## [[0.6.9](https://github.com/L-1124/QQMusicApi/compare/v0.6.8..v0.6.9)] - 2026-07-12
|
|
3
19
|
|
|
4
20
|
### Bug 修复
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Pagination
|
|
2
|
+
|
|
3
|
+
QQMusicApi 提供了统一的分页体系:
|
|
4
|
+
|
|
5
|
+
* **`PaginatedRequest`**:具备连续翻页与批次刷新能力的请求描述符。既可直接 `await` 获取首批响应,也可通过 `.pager()`
|
|
6
|
+
手动按需步进,或使用 `.paginate()`、`.collect()` 和 `async for` 进行流式与批量遍历。
|
|
7
|
+
* **`ItemPaginatedRequest`**:具备数据项提取能力的分页扩展类。除具备通用分页方法外,还通过 `.iter_items()` 与
|
|
8
|
+
`.collect_items()` 实现了跨越页面边界、直接消费单一具体业务元素(如歌曲、专辑等)的功能。
|
|
9
|
+
|
|
10
|
+
## 1. 单次请求与无状态步进
|
|
11
|
+
|
|
12
|
+
即使请求具备分页能力,你依然可以像普通请求一样直接 `await` 它,仅拉取单页数据:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import asyncio
|
|
16
|
+
from qqmusic_api import Client
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def main() -> None:
|
|
20
|
+
async with Client() as client:
|
|
21
|
+
# 仅获取第 1 页数据
|
|
22
|
+
first_page = await client.album.get_new_album(page=1, num=10)
|
|
23
|
+
print(len(first_page.albums))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
asyncio.run(main())
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
如果你希望配合上一次响应手动构建下一页请求:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
req1 = client.album.get_new_album(page=1, num=10)
|
|
33
|
+
res1 = await req1
|
|
34
|
+
|
|
35
|
+
# 根据上一次响应获取下一次请求的描述符
|
|
36
|
+
req2 = req1.next_request(res1)
|
|
37
|
+
if req2 is not None:
|
|
38
|
+
res2 = await req2
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 2. 有状态控制器
|
|
42
|
+
|
|
43
|
+
通过 `.pager()` 可以创建一个有状态的 `AsyncPager` 控制器:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import asyncio
|
|
47
|
+
from qqmusic_api import Client
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def main() -> None:
|
|
51
|
+
async with Client() as client:
|
|
52
|
+
pager = client.comment.get_hot_comments(102065756, page_size=5).pager(limit=2)
|
|
53
|
+
|
|
54
|
+
while pager.has_more():
|
|
55
|
+
page = await pager.next()
|
|
56
|
+
print(len(page.comments))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
asyncio.run(main())
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> `has_more()` 只读取当前分页器的内部状态,不会发起网络请求。`next()` 没有更多数据时会抛出 `StopAsyncIteration`。
|
|
63
|
+
|
|
64
|
+
## 3. 全量收集与条目平铺
|
|
65
|
+
|
|
66
|
+
如果你希望直接获取多页响应列表,或者直接获取展平后的所有实体数据项(如所有歌曲或专辑)。为防止无休止拉取带来的耗时与风控风险,强烈建议调用时始终设置合理的
|
|
67
|
+
`limit` 参数:
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import asyncio
|
|
71
|
+
from qqmusic_api import Client
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def main() -> None:
|
|
75
|
+
async with Client() as client:
|
|
76
|
+
req = client.singer.get_album_list(mid="0025NhlN2yWrP4")
|
|
77
|
+
|
|
78
|
+
# 收集前 3 页的 Response 响应对象列表
|
|
79
|
+
pages = await req.collect(limit=3)
|
|
80
|
+
print(f"共获取 {len(pages)} 页响应")
|
|
81
|
+
|
|
82
|
+
# 自动跨页展开提取前 25 个专辑实体
|
|
83
|
+
albums = await req.collect_items(limit=25)
|
|
84
|
+
print(f"共收集 {len(albums)} 个专辑实体")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
asyncio.run(main())
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 4. 异步流式迭代
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import asyncio
|
|
94
|
+
from qqmusic_api import Client
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def main() -> None:
|
|
98
|
+
async with Client() as client:
|
|
99
|
+
req = client.search.search_by_type("周杰伦", num=5)
|
|
100
|
+
|
|
101
|
+
# 方式 1:直接迭代对象本身,等价于 paginate(),连续翻页直至尾页
|
|
102
|
+
async for page in req:
|
|
103
|
+
print("当前页歌曲数:", len(page.song))
|
|
104
|
+
break # 演示示例:仅处理一页后退出
|
|
105
|
+
|
|
106
|
+
# 方式 2:显式限制最大翻页数(推荐在生产环境中为循环设置合理的上限)
|
|
107
|
+
async for page in req.paginate(limit=2):
|
|
108
|
+
print("当前页歌曲数:", len(page.song))
|
|
109
|
+
|
|
110
|
+
# 方式 3:跨页条目级别迭代(自动展平为实体)
|
|
111
|
+
async for song in req.iter_items(limit=10):
|
|
112
|
+
print("歌曲名:", song.name)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
asyncio.run(main())
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 5. 批次刷新与单批次步进
|
|
119
|
+
|
|
120
|
+
部分关联或推荐类接口(如歌曲相关 MV、相似歌曲等)并非按传统的页码(Page)或偏移量(Offset)递增,而是 **按批次(Batch)** 持续更新内容。
|
|
121
|
+
|
|
122
|
+
对于此类以批次刷新为主、常通过单次触发拉取的场景,推荐使用有状态的分页控制器 `.pager()` 配合 `.first()` 与 `.next()`
|
|
123
|
+
精准控制每一批次的获取:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import asyncio
|
|
127
|
+
from qqmusic_api import Client
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
async def main() -> None:
|
|
131
|
+
async with Client() as client:
|
|
132
|
+
# 1. 实例化分页控制器
|
|
133
|
+
pager = client.song.get_related_mv(1114857).pager()
|
|
134
|
+
|
|
135
|
+
# 2. 首次加载页面时,拉取首批推荐数据
|
|
136
|
+
first_batch = await pager.first()
|
|
137
|
+
print("首批 MV 数量:", len(first_batch.mv))
|
|
138
|
+
|
|
139
|
+
# 3. 按需触发:调用 pager.next() 刷新拉取下一个批次
|
|
140
|
+
if pager.has_more():
|
|
141
|
+
next_batch = await pager.next()
|
|
142
|
+
print("下一批 MV 数量:", len(next_batch.mv))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
asyncio.run(main())
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
通过 `pager().first()` 与 `pager().next()`,既能在规范的接口契约下享受自动游标维护与防重复终止保护,又能贴合按批次更新的数据消费模式。
|
|
149
|
+
|
|
150
|
+
## 6. 动态数据项提取
|
|
151
|
+
|
|
152
|
+
如果你使用的某个 API 返回的请求对象是原生的 `PaginatedRequest`(即 API 层没有预设数据项提取器),你仍然可以通过
|
|
153
|
+
`.with_extractor()` 动态注入一个提取逻辑。这会将请求无缝转换为具备跨页提取能力的 `ItemPaginatedRequest`。
|
|
154
|
+
|
|
155
|
+
这在处理一些层级较深、或者没有统一结构的响应时非常有用:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
import asyncio
|
|
159
|
+
from qqmusic_api import Client
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
async def main() -> None:
|
|
163
|
+
async with Client() as client:
|
|
164
|
+
# 这个 API 返回原生的 PaginatedRequest
|
|
165
|
+
req = client.search.general_search("周杰伦")
|
|
166
|
+
|
|
167
|
+
# 动态绑定 extractor
|
|
168
|
+
# 此时 item_req 类型变为 ItemPaginatedRequest
|
|
169
|
+
item_req = req.with_extractor(lambda r: r.song.items if r.song else [])
|
|
170
|
+
|
|
171
|
+
# 现在你可以非常自然地跨页迭代数据项了!
|
|
172
|
+
async for song in item_req.iter_items(limit=10):
|
|
173
|
+
print("提取到的歌曲:", song)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
asyncio.run(main())
|
|
177
|
+
```
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
default_install_hook_types = ["pre-commit", "commit-msg"]
|
|
2
|
+
exclude = '^docs/release-notes\.md$'
|
|
2
3
|
|
|
3
4
|
[[repos]]
|
|
4
|
-
repo = "
|
|
5
|
-
rev = "v6.0.0"
|
|
5
|
+
repo = "builtin"
|
|
6
6
|
hooks = [
|
|
7
7
|
{ id = "check-added-large-files" },
|
|
8
8
|
{ id = "check-merge-conflict" },
|
|
9
9
|
{ id = "check-json" },
|
|
10
10
|
{ id = "check-toml" },
|
|
11
|
-
{ id = "check-yaml"
|
|
11
|
+
{ id = "check-yaml" },
|
|
12
12
|
{ id = "detect-private-key" },
|
|
13
13
|
{ id = "end-of-file-fixer", exclude = '^LICENSE|\.(html|csv|txt|svg)$' },
|
|
14
14
|
{ id = "pretty-format-json", args = ["--autofix", "--no-ensure-ascii", "--no-sort-keys"] },
|
|
@@ -17,7 +17,7 @@ hooks = [
|
|
|
17
17
|
|
|
18
18
|
[[repos]]
|
|
19
19
|
repo = "https://github.com/commitizen-tools/commitizen"
|
|
20
|
-
rev = "v4.
|
|
20
|
+
rev = "v4.17.0"
|
|
21
21
|
hooks = [
|
|
22
22
|
{ id = "commitizen", additional_dependencies = ["cz-conventional-gitmoji"] },
|
|
23
23
|
]
|
|
@@ -31,7 +31,7 @@ hooks = [
|
|
|
31
31
|
|
|
32
32
|
[[repos]]
|
|
33
33
|
repo = "https://github.com/DavidAnson/markdownlint-cli2"
|
|
34
|
-
rev = "v0.
|
|
34
|
+
rev = "v0.23.2"
|
|
35
35
|
hooks = [
|
|
36
36
|
{ id = "markdownlint-cli2", args = ["--fix"] },
|
|
37
37
|
]
|
|
@@ -39,7 +39,6 @@ dependencies = [
|
|
|
39
39
|
"orjson>=3.10.15",
|
|
40
40
|
"paho-mqtt>=2.1.0",
|
|
41
41
|
"pydantic>=2.13.3",
|
|
42
|
-
"tarsio>=0.5.1",
|
|
43
42
|
"typing-extensions>=4.12.2",
|
|
44
43
|
]
|
|
45
44
|
dynamic = ["version"]
|
|
@@ -56,7 +55,6 @@ upload = [
|
|
|
56
55
|
|
|
57
56
|
[dependency-groups]
|
|
58
57
|
dev = [
|
|
59
|
-
"tarsio[cli]>=0.5.1",
|
|
60
58
|
"terminal-qrcode>=1.1.0",
|
|
61
59
|
{ include-group = "docs" },
|
|
62
60
|
{ include-group = "linting" },
|