qqmusic-api-python 0.7.1__tar.gz → 0.7.2__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.1 → qqmusic_api_python-0.7.2}/AGENTS.md +6 -3
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/PKG-INFO +1 -1
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/coding.md +64 -68
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/contributing.md +1 -9
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/release-notes.md +42 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/client.md +46 -4
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/pagination.md +6 -6
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/pyproject.toml +3 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/__init__.py +1 -1
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/__init__.py +6 -2
- qqmusic_api_python-0.7.2/qqmusic_api/core/api_context.py +227 -0
- qqmusic_api_python-0.7.2/qqmusic_api/core/client.py +531 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/pagination.py +240 -24
- qqmusic_api_python-0.7.2/qqmusic_api/core/request.py +371 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/search.py +174 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/singer.py +28 -1
- qqmusic_api_python-0.7.2/qqmusic_api/modules/_base.py +231 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/album.py +5 -5
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/comment.py +7 -7
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper.py +2 -2
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login.py +38 -32
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/lyric.py +6 -7
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/mv.py +3 -3
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/private_message.py +18 -19
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/recommend.py +5 -5
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/search.py +32 -22
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/singer.py +32 -11
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/song.py +14 -14
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/songlist.py +5 -5
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/top.py +2 -2
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/user.py +19 -19
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/conftest.py +5 -2
- qqmusic_api_python-0.7.2/tests/test_api_context.py +356 -0
- qqmusic_api_python-0.7.2/tests/test_client.py +335 -0
- qqmusic_api_python-0.7.2/tests/test_pagination.py +490 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_private_message.py +1 -1
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_search.py +4 -4
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/uv.lock +34 -34
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/requirements.txt +435 -489
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/singer.py +2 -1
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/_helpers.py +7 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/search.py +10 -4
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/singer.py +14 -3
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/router_factory.py +3 -3
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/zensical.toml +0 -1
- qqmusic_api_python-0.7.1/qqmusic_api/core/client.py +0 -626
- qqmusic_api_python-0.7.1/qqmusic_api/core/request.py +0 -220
- qqmusic_api_python-0.7.1/qqmusic_api/modules/_base.py +0 -225
- qqmusic_api_python-0.7.1/tests/test_pagination.py +0 -401
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.agents/skills/pydantic/SKILL.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.agents/skills/python-standards/SKILL.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.agents/skills/uv-package-manager/SKILL.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.dockerignore +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/renovate.json +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/checking.yaml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/docs.yml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/release.yml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/testing.yml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.gitignore +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.markdownlint-cli2.yaml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/CLAUDE.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/LICENSE +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/README.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/assets/qq-music.svg +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/cliff.toml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/index.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/client.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/exception.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/pagination.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/request.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/versioning.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/album.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/base.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/comment.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/helper.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/login.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/lyric.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/mv.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/private_message.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/recommend.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/request.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/search.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/singer.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/song.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/songlist.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/top.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/user.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/album.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/comment.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/helper.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/helper_utils.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/login.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/login_utils.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/lyric.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/mv.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/private_message.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/recommend.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/search.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/singer.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/song.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/songlist.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/top.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/user.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/credential.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/download.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/error-handling.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/login.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/start.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/web.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/download_song.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/get_all_sheets.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/phone_login.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/private_message.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/qrcode_login.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/upload_file.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/prek.toml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/sign.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/tripledes.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/exceptions.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/versioning.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/_validator.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/album.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/base.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/comment.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/helper.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/login.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/lyric.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/mv.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/private_message.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/recommend.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/request.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/song.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/songlist.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/top.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/user.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper_utils.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login_utils.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/mqtt.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/scripts/ag-1.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_album.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_comment.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_login.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_login_utils.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_lyric.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_mv.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_recommend.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_song.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_user.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/.gitignore +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/Dockerfile +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/README.md +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/accounts.example.toml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/config.example.toml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/docker-compose.example.yml +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/run.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/app.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/auth.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/cache.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/config.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/credential_store.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/deps.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/response.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/security.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/comment.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/login.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/mv.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/song.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/songlist.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/album.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/comment.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/login.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/lyric.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/mv.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/recommend.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/song.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/songlist.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/top.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/user.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/__init__.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/adapter_registry.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/docstrings.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/executor.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/params.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/route_types.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/conftest.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_core.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_docstrings.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_enums.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_route_validation.py +0 -0
- {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_routes.py +0 -0
|
@@ -23,9 +23,12 @@
|
|
|
23
23
|
|
|
24
24
|
## Testing rules
|
|
25
25
|
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* **双重视角测试**:测试按层级分为 Modules 与 Core 两类视角,均以黑盒方式覆盖可观察行为:
|
|
27
|
+
* **Modules 层(集成测试)**:测试重心,视为黑盒,采用基于数据驱动(`@pytest.mark.parametrize`)的函数式测试方法,将输入参数与期望结果的特征断言解耦。
|
|
28
|
+
* **Core 层(单元测试)**:必要的核心逻辑测试,覆盖请求分组、批量合并、参数拼装、异常转换、分页推进等纯逻辑,不发起任何网络请求,保证测试确定性与快速性。
|
|
29
|
+
* **Modules 层真实网络请求(No Mock)**:禁止 Mock 底层网络请求或核心组装逻辑。必须直接与真实的 QQ 音乐 API 交互,以验证接口连通性、参数拼装和数据模型(Models)解析的正确性。
|
|
30
|
+
* **Core 层桩数据(Stub Only)**:仅允许使用自定义桩(如 `DummyResponse`、`MockClient`)驱动被测逻辑,禁止 Mock 被测对象自身的内部行为;不得依赖真实网络。
|
|
31
|
+
* **优雅处理限流(Rate Limit)**:由于 Modules 层采用真实网络请求,当触发上游 API 的风控或频率限制异常时,必须使用自定义装饰器捕获该异常并调用 `pytest.skip()` 安全跳过,严禁因此导致测试失败。
|
|
29
32
|
* **平铺函数写法**:摒弃测试类(`class TestXXX`),强制采用平铺的独立纯函数(Flat Functions)编写测试用例,通过 fixtures 注入依赖,保证测试的独立性。
|
|
30
33
|
* 测试用例必须包含单行中文 docstring,且 docstring 内部必须使用英文标点符号。
|
|
31
34
|
* 优先在现有的测试文件中添加用例,仅在测试全新模块时才允许创建新的测试文件。
|
|
@@ -11,12 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
13
|
模块方法
|
|
14
|
-
-> self.
|
|
15
|
-
->
|
|
14
|
+
-> self._build_cgi(...) / self._build_http(...)
|
|
15
|
+
-> BaseRequest 描述符
|
|
16
16
|
-> await request
|
|
17
17
|
-> Client.execute(request)
|
|
18
|
-
->
|
|
19
|
-
->
|
|
18
|
+
-> ApiContext 注入环境与凭证
|
|
19
|
+
-> Session.post(...) / Session.request(...)
|
|
20
|
+
-> Request._parse_response(...)
|
|
20
21
|
-> 返回原始 dict 或 Pydantic 模型
|
|
21
22
|
```
|
|
22
23
|
|
|
@@ -24,18 +25,18 @@
|
|
|
24
25
|
|
|
25
26
|
```text
|
|
26
27
|
多个模块方法
|
|
27
|
-
-> self.
|
|
28
|
-
->
|
|
28
|
+
-> self._build_cgi(...)
|
|
29
|
+
-> BaseRequest 描述符列表
|
|
29
30
|
-> Client.gather(requests)
|
|
30
|
-
->
|
|
31
|
+
-> 按协议、平台、公共参数和凭证配置键自动分组
|
|
31
32
|
-> 每组按 batch_size 拆分为批量请求
|
|
32
|
-
->
|
|
33
|
-
-> 使用客户端内部的
|
|
34
|
-
->
|
|
35
|
-
->
|
|
33
|
+
-> 依次发起合并的多参 CGI 请求(req_0, req_1...)
|
|
34
|
+
-> 使用客户端内部的 Session 并发执行这些任务(self._session.gather)
|
|
35
|
+
-> 统一解包解析每个响应项
|
|
36
|
+
-> 按输入顺序返回结果列表
|
|
36
37
|
```
|
|
37
38
|
|
|
38
|
-
`gather` 的分组边界由 `
|
|
39
|
+
`gather` 的分组边界由 `BaseRequest._group_key` 决定。只有协议类型、显式平台、公共参数和凭证相同的请求才会安全地合并到同一个批量请求中。
|
|
39
40
|
|
|
40
41
|
## 编写新的 API
|
|
41
42
|
|
|
@@ -55,7 +56,7 @@ class FooApi(ApiModule):
|
|
|
55
56
|
|
|
56
57
|
def get_something(self, id: int):
|
|
57
58
|
"""获取某项数据."""
|
|
58
|
-
return self.
|
|
59
|
+
return self._build_cgi(
|
|
59
60
|
module="music.foo.Svc",
|
|
60
61
|
method="GetSomething",
|
|
61
62
|
param={"id": id},
|
|
@@ -76,83 +77,78 @@ class Client:
|
|
|
76
77
|
|
|
77
78
|
### 添加新的请求方法
|
|
78
79
|
|
|
79
|
-
API 方法返回 `
|
|
80
|
+
API 方法返回 `BaseRequest` 描述符对象,并不立即发起请求。对于标准 CGI 风格的 RPC 请求,使用 `self._build_cgi(...)` 工厂方法构建:
|
|
80
81
|
|
|
81
82
|
```python
|
|
82
83
|
def get_detail(self, song_id: int):
|
|
83
84
|
"""获取歌曲详情."""
|
|
84
|
-
return self.
|
|
85
|
+
return self._build_cgi(
|
|
85
86
|
module="music.songDetail", # 接口所属模块
|
|
86
87
|
method="GetDetail", # 方法名
|
|
87
88
|
param={"songid": song_id}, # 业务参数
|
|
88
89
|
)
|
|
89
90
|
```
|
|
90
91
|
|
|
91
|
-
对于非标准 CGI 接口(如直接 GET
|
|
92
|
+
对于非标准 CGI 接口(如直接 GET 请求、获取网页或二维码),使用 `self._build_http(...)`:
|
|
92
93
|
|
|
93
94
|
```python
|
|
94
95
|
async def quick_search(self, keyword: str) -> dict[str, Any]:
|
|
95
96
|
"""快速搜索 (直接返回解析后的 JSON 数据)."""
|
|
96
|
-
resp = await self.
|
|
97
|
+
resp = await self._build_http(
|
|
97
98
|
"GET",
|
|
98
99
|
"https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg",
|
|
99
100
|
params={"key": keyword},
|
|
100
101
|
)
|
|
101
|
-
resp
|
|
102
|
-
return resp.json()["data"]
|
|
102
|
+
return resp["data"]
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
### `
|
|
106
|
-
|
|
107
|
-
| 参数
|
|
108
|
-
|
|
109
|
-
| `module`
|
|
110
|
-
| `method`
|
|
111
|
-
| `param`
|
|
112
|
-
| `response_model`
|
|
113
|
-
| `comm`
|
|
114
|
-
| `override_comm`
|
|
115
|
-
| `credential`
|
|
116
|
-
| `platform`
|
|
117
|
-
| `preserve_bool`
|
|
118
|
-
| `sign`
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
|
|
122
|
-
### `
|
|
123
|
-
|
|
124
|
-
`
|
|
125
|
-
|
|
126
|
-
| 参数
|
|
127
|
-
|
|
128
|
-
| `method`
|
|
129
|
-
| `url`
|
|
130
|
-
| `credential`
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `**kwargs` | | 透传给底层 `niquests` 的参数(`params`、`json`、`data`、`headers`、`cookies` 等) |
|
|
105
|
+
### `_build_cgi` 参数说明
|
|
106
|
+
|
|
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
|
+
| `require_login` | `bool` | 是否在执行时强制校验用户登录态 |
|
|
120
|
+
| `pager_strategy` | `PagerStrategy` 或 `None` | 分页策略,提供后返回 `PaginatedCgiRequest`;可链式调用 `.with_extractor()` 提升为 `ItemPaginatedCgiRequest` |
|
|
121
|
+
|
|
122
|
+
### `_build_http` 参数说明
|
|
123
|
+
|
|
124
|
+
`_build_http` 用于构建标准 HTTP 请求描述符,自动装配凭证 Cookies 和平台 User-Agent:
|
|
125
|
+
|
|
126
|
+
| 参数 | 类型 | 说明 |
|
|
127
|
+
|-----------------|------------------------|----------------------------------------------------------------------------------:|
|
|
128
|
+
| `method` | `str` | HTTP 方法,如 `"GET"`、`"POST"` |
|
|
129
|
+
| `url` | `str` | 请求地址 |
|
|
130
|
+
| `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证,默认使用客户端凭证 |
|
|
131
|
+
| `disable_parse` | `bool` | 为 True 时不解析 JSON,直接返回原始 `niquests.Response` 对象 |
|
|
132
|
+
| `**kwargs` | | 透传给底层 `niquests` 的参数(`params`、`json`、`data`、`headers`、`cookies` 等) |
|
|
134
133
|
|
|
135
134
|
!!! note
|
|
136
135
|
|
|
137
|
-
`
|
|
136
|
+
`_build_cgi` 返回 `CgiRequest`,`_build_http` 返回 `HttpRequest`,两者均继承自 `BaseRequest`。它们都支持直接被 `await` 以触发网络请求并自动完成响应验证和模型解析。
|
|
138
137
|
|
|
139
138
|
常见用法:
|
|
140
139
|
|
|
141
140
|
```python
|
|
142
141
|
# GET 请求
|
|
143
|
-
|
|
142
|
+
req = self._build_http("GET", "https://example.com/api", params={"key": "value"})
|
|
144
143
|
|
|
145
144
|
# POST JSON
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
# POST form data
|
|
149
|
-
resp = await client.request("POST", "https://example.com/api", data={"key": "value"})
|
|
150
|
-
|
|
151
|
-
# 自定义 headers
|
|
152
|
-
resp = await client.request("GET", "https://example.com/api", headers={"X-Custom": "value"})
|
|
145
|
+
req = self._build_http("POST", "https://example.com/api", json={"key": "value"})
|
|
153
146
|
|
|
154
147
|
# 覆盖凭证
|
|
155
|
-
|
|
148
|
+
req = self._build_http("GET", "https://example.com/api", credential=my_credential)
|
|
149
|
+
|
|
150
|
+
# 返回原始 Response 而非解析 JSON
|
|
151
|
+
req = self._build_http("GET", "https://example.com/api", disable_parse=True)
|
|
156
152
|
```
|
|
157
153
|
|
|
158
154
|
## 响应模型
|
|
@@ -232,12 +228,12 @@ class Singer(Response):
|
|
|
232
228
|
|
|
233
229
|
### 需登录的接口
|
|
234
230
|
|
|
235
|
-
需要登录的接口通过 `
|
|
231
|
+
需要登录的接口通过 `_build_cgi` 的 `require_login` 参数校验凭证:
|
|
236
232
|
|
|
237
233
|
```python
|
|
238
234
|
def get_vip_info(self, *, credential: Credential | None = None):
|
|
239
235
|
"""获取 VIP 信息."""
|
|
240
|
-
return self.
|
|
236
|
+
return self._build_cgi(
|
|
241
237
|
module="VipLogin.VipLoginInter",
|
|
242
238
|
method="vip_login_base",
|
|
243
239
|
param={},
|
|
@@ -247,8 +243,8 @@ def get_vip_info(self, *, credential: Credential | None = None):
|
|
|
247
243
|
)
|
|
248
244
|
```
|
|
249
245
|
|
|
250
|
-
>
|
|
251
|
-
>
|
|
246
|
+
> 若接口需要凭证对象的属性(如 `musicid` 等)来构建请求内联参数,
|
|
247
|
+
> 仍可通过 `credential = credential or self._client.credential` 获取并显式验证其有效性。
|
|
252
248
|
|
|
253
249
|
## 连续翻页与批次刷新
|
|
254
250
|
|
|
@@ -264,7 +260,7 @@ from ..core.pagination import OffsetStrategy
|
|
|
264
260
|
|
|
265
261
|
def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
|
|
266
262
|
"""获取歌单详情."""
|
|
267
|
-
return self.
|
|
263
|
+
return self._build_cgi(
|
|
268
264
|
module="music.srfDissInfo.DissInfo",
|
|
269
265
|
method="CgiGetDiss",
|
|
270
266
|
param={
|
|
@@ -294,7 +290,7 @@ from ..models.base import MV
|
|
|
294
290
|
|
|
295
291
|
def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
296
292
|
"""获取歌曲相关 MV."""
|
|
297
|
-
return self.
|
|
293
|
+
return self._build_cgi(
|
|
298
294
|
module="MvService.MvInfoProServer",
|
|
299
295
|
method="GetSongRelatedMv",
|
|
300
296
|
param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
|
|
@@ -324,7 +320,7 @@ def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
|
324
320
|
```python
|
|
325
321
|
def get_sheet(self, mid: str):
|
|
326
322
|
"""获取曲谱."""
|
|
327
|
-
return self.
|
|
323
|
+
return self._build_cgi(
|
|
328
324
|
module="music.mir.SheetMusicSvr",
|
|
329
325
|
method="GetMoreSheetMusic",
|
|
330
326
|
param={"songMid": mid},
|
|
@@ -340,7 +336,7 @@ def get_sheet(self, mid: str):
|
|
|
340
336
|
|
|
341
337
|
```python
|
|
342
338
|
# 合并到自动生成的 comm 中(默认行为)
|
|
343
|
-
self.
|
|
339
|
+
self._build_cgi(
|
|
344
340
|
...,
|
|
345
341
|
comm={"extra_key": "value"},
|
|
346
342
|
)
|
|
@@ -349,7 +345,7 @@ self._build_request(
|
|
|
349
345
|
使用 `override_comm=True` 完全替代自动生成的参数:
|
|
350
346
|
|
|
351
347
|
```python
|
|
352
|
-
self.
|
|
348
|
+
self._build_cgi(
|
|
353
349
|
...,
|
|
354
350
|
comm={
|
|
355
351
|
"g_tk": 5381,
|
|
@@ -373,7 +369,7 @@ self._build_request(
|
|
|
373
369
|
from ..core.exceptions import ApiDataError
|
|
374
370
|
|
|
375
371
|
try:
|
|
376
|
-
|
|
372
|
+
...
|
|
377
373
|
except KeyError as e:
|
|
378
374
|
raise ApiDataError("无法解析歌曲信息") from e
|
|
379
375
|
```
|
|
@@ -76,19 +76,11 @@ 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
|
-
|
|
87
79
|
## 代码注释
|
|
88
80
|
|
|
89
81
|
* 注释内容包括:模块注释、类注释、函数注释、参数类型注释、返回值注释
|
|
90
82
|
* 注释风格遵循 [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
|
91
|
-
*
|
|
83
|
+
* 测试用例必须包含 **单行中文 docstring**,且内部必须使用 **英文标点符号**。
|
|
92
84
|
|
|
93
85
|
## 文档规范
|
|
94
86
|
|
|
@@ -1,4 +1,46 @@
|
|
|
1
1
|
|
|
2
|
+
## [[0.7.1](https://github.com/L-1124/QQMusicApi/compare/v0.7.0..v0.7.1)] - 2026-08-02
|
|
3
|
+
|
|
4
|
+
### Bug 修复
|
|
5
|
+
|
|
6
|
+
* **(Song)** 移除 SongApi 中不必要的参数说明 ([e4b02c7](https://github.com/L-1124/QQMusicApi/commit/e4b02c7b0c4ccc8b7310701985bb5c6d62e21f4f)) by [@L-1124](https://github.com/L-1124)
|
|
7
|
+
* **(web)** 使用 context.credential 修复 adapter 凭证读取 KeyError ([261326e](https://github.com/L-1124/QQMusicApi/commit/261326eec051e7f444296b5c461e7412c4b25bb9)) by [@L-1124](https://github.com/L-1124)
|
|
8
|
+
* **(web)** 修复 404 响应不符合 RESTful 规范的问题 ([9658bd5](https://github.com/L-1124/QQMusicApi/commit/9658bd54b45c8eeab75f651ba26b16e1fd1126ce)) by [@L-1124](https://github.com/L-1124)
|
|
9
|
+
* **(web)** 修复 Web 模块代码质量与性能问题 ([329f822](https://github.com/L-1124/QQMusicApi/commit/329f822776376aad7bd6b3ede3e9dd9e5730c77c)) by [@L-1124](https://github.com/L-1124)
|
|
10
|
+
|
|
11
|
+
### Dep-bump
|
|
12
|
+
|
|
13
|
+
* **(deps)** update dependencies via uv lock --upgrade ([943ed52](https://github.com/L-1124/QQMusicApi/commit/943ed522e8bf6d385e3a0abb77663255ea57ea25)) by [@L-1124](https://github.com/L-1124)
|
|
14
|
+
|
|
15
|
+
### 功能更新
|
|
16
|
+
|
|
17
|
+
* **(comment)** 支持设置评论业务类型与子类型 ([ea6095c](https://github.com/L-1124/QQMusicApi/commit/ea6095ce89772798154cf85785a2d72ee9a57ac4)) by [@L-1124](https://github.com/L-1124)
|
|
18
|
+
* **(lyric)** 支持获取 AI 歌词词典 ([44e81f0](https://github.com/L-1124/QQMusicApi/commit/44e81f0521d73934a3bedacc3ec64870549c5f9c)) by [@L-1124](https://github.com/L-1124)
|
|
19
|
+
* **(lyric)** 支持获取多风格翻译歌词 ([43d6429](https://github.com/L-1124/QQMusicApi/commit/43d64299a3e57779859f4ba5f876f0b11198e2ab)) by [@L-1124](https://github.com/L-1124)
|
|
20
|
+
* **(lyric)** 支持获取助唱标注歌词及信息 ([3166fea](https://github.com/L-1124/QQMusicApi/commit/3166feabf77e223b82dca6922f4e09dd5eed0a15)) by [@L-1124](https://github.com/L-1124)
|
|
21
|
+
* **(search)** 支持彩铃搜索类型 RINGTONE 与彩铃文件类型 RingSongFileType ([6c2118c](https://github.com/L-1124/QQMusicApi/commit/6c2118ccdd6e1e4eed9dd49d82b12bf63664630d)) by [@L-1124](https://github.com/L-1124)
|
|
22
|
+
* **(search)** 类型搜索支持选择器 ([e097442](https://github.com/L-1124/QQMusicApi/commit/e097442d2a7e86f7f77bef016416554591370f41)) by [@L-1124](https://github.com/L-1124)
|
|
23
|
+
* **(web)** 引入 AuthPolicy.OPTIONAL 并对齐 SDK 凭证策略 ([ea29202](https://github.com/L-1124/QQMusicApi/commit/ea29202766ebc9a309c7fb3db495c246323289f7)) by [@L-1124](https://github.com/L-1124)
|
|
24
|
+
* **(web)** 优化枚举参数在 OpenAPI 文档中的说明展示 ([075a1ed](https://github.com/L-1124/QQMusicApi/commit/075a1ed9d964d374b4d34759b7d5f1a3fed937fc)) by [@L-1124](https://github.com/L-1124)
|
|
25
|
+
* **(web)** 支持 list[BaseModel] 类型的 JSON Query 参数解析 ([9dd26ee](https://github.com/L-1124/QQMusicApi/commit/9dd26eeff3871c1a4990c58b23d497737e547ed2)) by [@L-1124](https://github.com/L-1124)
|
|
26
|
+
|
|
27
|
+
### 功能重构
|
|
28
|
+
|
|
29
|
+
* **(core)** [**breaking**] 重构并统一分页体系 ([cb41e16](https://github.com/L-1124/QQMusicApi/commit/cb41e16e62e9967010663a94db846dd205d45d1b)) by [@L-1124](https://github.com/L-1124) in [#299](https://github.com/L-1124/QQMusicApi/pull/299)
|
|
30
|
+
* **(core)** [**breaking**] 移除未使用的 JCE 协议支持 ([dca6c7c](https://github.com/L-1124/QQMusicApi/commit/dca6c7cb623942f02d7cad44b986a8c7fbc91858)) by [@L-1124](https://github.com/L-1124)
|
|
31
|
+
* **(lyric)** [**breaking**] 统一 song_id 为 songid ([e6d2a0b](https://github.com/L-1124/QQMusicApi/commit/e6d2a0baae21a3528115635b43a24781b9671017)) by [@L-1124](https://github.com/L-1124)
|
|
32
|
+
* **(web)** 引入 Adapter 注册表机制解耦路由声明 ([8b573c6](https://github.com/L-1124/QQMusicApi/commit/8b573c6a4da79e691762ccf9d81390730260b13c)) by [@L-1124](https://github.com/L-1124)
|
|
33
|
+
|
|
34
|
+
### 文档更新
|
|
35
|
+
|
|
36
|
+
* **(api)** 补充缺失的 API 模块和数据模型文档映射 ([8fe6095](https://github.com/L-1124/QQMusicApi/commit/8fe6095b154b35406fa2587e585d1f82373720ac)) by [@L-1124](https://github.com/L-1124)
|
|
37
|
+
|
|
38
|
+
### 贡献者
|
|
39
|
+
|
|
40
|
+
* @L-1124
|
|
41
|
+
* @mirkosalvato1-ctrl
|
|
42
|
+
* @github-actions[bot]
|
|
43
|
+
|
|
2
44
|
## [[0.7.0](https://github.com/L-1124/QQMusicApi/compare/v0.6.9..v0.7.0)] - 2026-07-22
|
|
3
45
|
|
|
4
46
|
### Bug 修复
|
|
@@ -61,6 +61,48 @@ results = await client.gather(
|
|
|
61
61
|
|
|
62
62
|
此时失败项会以异常对象的形式出现在对应位置,成功项仍返回正常的响应模型。
|
|
63
63
|
|
|
64
|
+
默认情况下 `return_exceptions=False`,任一请求执行期间发生异常时,`gather()` 会中断并抛出 `ExceptionGroup`
|
|
65
|
+
(`BaseExceptionGroup` 的子类),其余尚未完成的并发请求会被取消。即使 **只有一个**请求失败,异常也会被包装成异常组抛出(通常包含触发失败的那个异常;当多个请求在同一轮取消/竞争中各自抛出新异常时,异常组可能包含多个)。
|
|
66
|
+
|
|
67
|
+
`except*` 需要 Python 3.11+;在 3.10 上可从 `exceptiongroup` 兼容包导入 `BaseExceptionGroup`。若不需要区分并发错误,也可以保留
|
|
68
|
+
`return_exceptions=True`,再对结果中的异常对象逐一处理。
|
|
69
|
+
|
|
70
|
+
=== "Python 3.11+"
|
|
71
|
+
|
|
72
|
+
使用 `except*` 按异常类型直接捕获:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
try:
|
|
76
|
+
results = await client.gather([...])
|
|
77
|
+
except* NetworkError as exc_group:
|
|
78
|
+
for exc in exc_group.exceptions:
|
|
79
|
+
print(f"网络错误: {exc}")
|
|
80
|
+
except* CgiApiException as exc_group:
|
|
81
|
+
for exc in exc_group.exceptions:
|
|
82
|
+
print(f"接口错误: {exc}")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
=== "Python 3.10"
|
|
86
|
+
|
|
87
|
+
Python 3.10 没有内置异常组, 从 `exceptiongroup` 兼容包导入后, 用普通 `except` 即可捕获:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from exceptiongroup import BaseExceptionGroup
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
results = await client.gather([...])
|
|
94
|
+
except BaseExceptionGroup as exc_group:
|
|
95
|
+
for exc in exc_group.exceptions:
|
|
96
|
+
if isinstance(exc, NetworkError):
|
|
97
|
+
print(f"网络错误: {exc}")
|
|
98
|
+
elif isinstance(exc, CgiApiException):
|
|
99
|
+
print(f"接口错误: {exc}")
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> 注意:默认 `return_exceptions=False` 时,一旦抛出异常组,本次 `gather` 将立即终止且 **不会返回任何结果**
|
|
103
|
+
> ——已成功的请求其结果也会一并丢弃,尚未执行的请求会被取消,异常组中也拿不到它们的异常。若需要保留成功项的结果、只对失败项单独处理,请使用
|
|
104
|
+
> `return_exceptions=True`。
|
|
105
|
+
|
|
64
106
|
## 全局凭证
|
|
65
107
|
|
|
66
108
|
如果你的场景需要登录,可以在初始化 `Client` 时直接注入 `Credential`:
|
|
@@ -92,11 +134,11 @@ asyncio.run(main())
|
|
|
92
134
|
|
|
93
135
|
支持的平台:
|
|
94
136
|
|
|
95
|
-
| 平台
|
|
96
|
-
|
|
137
|
+
| 平台 | `Platform` 值 | 说明 |
|
|
138
|
+
|---------|--------------------|----------------------|
|
|
97
139
|
| Android | `Platform.ANDROID` | 默认,大部分接口使用 |
|
|
98
|
-
| Desktop | `Platform.DESKTOP` | QQ 音乐桌面端
|
|
99
|
-
| Web
|
|
140
|
+
| Desktop | `Platform.DESKTOP` | QQ 音乐桌面端 |
|
|
141
|
+
| Web | `Platform.WEB` | QQ 音乐网页端 |
|
|
100
142
|
|
|
101
143
|
!!! note
|
|
102
144
|
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
QQMusicApi 提供了统一的分页体系:
|
|
4
4
|
|
|
5
|
-
* **`
|
|
5
|
+
* **`PaginatedCgiRequest`**:具备连续翻页与批次刷新能力的 CGI 请求描述符。既可直接 `await` 获取首批响应,也可通过 `.pager()`
|
|
6
6
|
手动按需步进,或使用 `.paginate()`、`.collect()` 和 `async for` 进行流式与批量遍历。
|
|
7
|
-
* **`
|
|
7
|
+
* **`ItemPaginatedCgiRequest`**:具备数据项提取能力的分页扩展类。除具备通用分页方法外,还通过 `.iter_items()` 与
|
|
8
8
|
`.collect_items()` 实现了跨越页面边界、直接消费单一具体业务元素(如歌曲、专辑等)的功能。
|
|
9
9
|
|
|
10
10
|
## 1. 单次请求与无状态步进
|
|
@@ -149,8 +149,8 @@ asyncio.run(main())
|
|
|
149
149
|
|
|
150
150
|
## 6. 动态数据项提取
|
|
151
151
|
|
|
152
|
-
如果你使用的某个 API 返回的请求对象是原生的 `
|
|
153
|
-
`.with_extractor()` 动态注入一个提取逻辑。这会将请求无缝转换为具备跨页提取能力的 `
|
|
152
|
+
如果你使用的某个 API 返回的请求对象是原生的 `PaginatedCgiRequest`(即 API 层没有预设数据项提取器),你仍然可以通过
|
|
153
|
+
`.with_extractor()` 动态注入一个提取逻辑。这会将请求无缝转换为具备跨页提取能力的 `ItemPaginatedCgiRequest`。
|
|
154
154
|
|
|
155
155
|
这在处理一些层级较深、或者没有统一结构的响应时非常有用:
|
|
156
156
|
|
|
@@ -161,11 +161,11 @@ from qqmusic_api import Client
|
|
|
161
161
|
|
|
162
162
|
async def main() -> None:
|
|
163
163
|
async with Client() as client:
|
|
164
|
-
# 这个 API 返回原生的
|
|
164
|
+
# 这个 API 返回原生的 PaginatedCgiRequest
|
|
165
165
|
req = client.search.general_search("周杰伦")
|
|
166
166
|
|
|
167
167
|
# 动态绑定 extractor
|
|
168
|
-
# 此时 item_req 类型变为
|
|
168
|
+
# 此时 item_req 类型变为 ItemPaginatedCgiRequest
|
|
169
169
|
item_req = req.with_extractor(lambda r: r.song.items if r.song else [])
|
|
170
170
|
|
|
171
171
|
# 现在你可以非常自然地跨页迭代数据项了!
|
|
@@ -19,7 +19,7 @@ from .exceptions import (
|
|
|
19
19
|
NetworkError,
|
|
20
20
|
RatelimitedError,
|
|
21
21
|
)
|
|
22
|
-
from .request import
|
|
22
|
+
from .request import BaseRequest, CgiRequest, HttpRequest, ItemPaginatedCgiRequest, PaginatedCgiRequest
|
|
23
23
|
from .versioning import DEFAULT_VERSION_POLICY, Platform, VersionPolicy, VersionProfile
|
|
24
24
|
|
|
25
25
|
__all__ = [
|
|
@@ -27,22 +27,26 @@ __all__ = [
|
|
|
27
27
|
"ApiDataError",
|
|
28
28
|
"ApiException",
|
|
29
29
|
"BaseApiException",
|
|
30
|
+
"BaseRequest",
|
|
30
31
|
"CgiApiException",
|
|
32
|
+
"CgiRequest",
|
|
31
33
|
"Client",
|
|
32
34
|
"CredentialExpiredError",
|
|
33
35
|
"CredentialInvalidError",
|
|
34
36
|
"CredentialRefreshError",
|
|
35
37
|
"GlobalApiError",
|
|
36
38
|
"HTTPError",
|
|
39
|
+
"HttpRequest",
|
|
40
|
+
"ItemPaginatedCgiRequest",
|
|
37
41
|
"LoginAccountRestrictedError",
|
|
38
42
|
"LoginAuthExpiredError",
|
|
39
43
|
"LoginDeviceLimitError",
|
|
40
44
|
"LoginError",
|
|
41
45
|
"LoginRateLimitError",
|
|
42
46
|
"NetworkError",
|
|
47
|
+
"PaginatedCgiRequest",
|
|
43
48
|
"Platform",
|
|
44
49
|
"RatelimitedError",
|
|
45
|
-
"Request",
|
|
46
50
|
"VersionPolicy",
|
|
47
51
|
"VersionProfile",
|
|
48
52
|
]
|