qqmusic-api-python 0.7.0__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.0 → qqmusic_api_python-0.7.2}/.github/workflows/docs.yml +6 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/workflows/release.yml +6 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/workflows/testing.yml +2 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/AGENTS.md +6 -3
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/PKG-INFO +1 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/coding.md +125 -116
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/contributing.md +1 -1
- qqmusic_api_python-0.7.2/docs/reference/model/helper.md +3 -0
- qqmusic_api_python-0.7.2/docs/reference/model/private_message.md +3 -0
- qqmusic_api_python-0.7.2/docs/reference/modules/helper.md +3 -0
- qqmusic_api_python-0.7.2/docs/reference/modules/helper_utils.md +3 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/release-notes.md +58 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/client.md +46 -4
- qqmusic_api_python-0.7.2/docs/tutorial/pagination.md +177 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/prek.toml +5 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/pyproject.toml +3 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/__init__.py +1 -1
- {qqmusic_api_python-0.7.0 → 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.2/qqmusic_api/core/pagination.py +680 -0
- qqmusic_api_python-0.7.2/qqmusic_api/core/request.py +371 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/comment.py +18 -0
- qqmusic_api_python-0.7.2/qqmusic_api/models/lyric.py +136 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/request.py +0 -30
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/search.py +193 -0
- {qqmusic_api_python-0.7.0 → 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.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/album.py +18 -14
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/comment.py +108 -69
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper.py +2 -8
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login.py +38 -32
- qqmusic_api_python-0.7.2/qqmusic_api/modules/lyric.py +130 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/mv.py +10 -8
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/private_message.py +35 -36
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/recommend.py +28 -42
- qqmusic_api_python-0.7.2/qqmusic_api/modules/search.py +299 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/singer.py +68 -46
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/song.py +38 -33
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/songlist.py +13 -14
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/top.py +9 -10
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/user.py +85 -88
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/mqtt.py +4 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/conftest.py +5 -4
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_album.py +4 -4
- 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.0 → qqmusic_api_python-0.7.2}/tests/test_comment.py +27 -13
- qqmusic_api_python-0.7.2/tests/test_lyric.py +70 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_mv.py +4 -4
- qqmusic_api_python-0.7.2/tests/test_pagination.py +490 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_private_message.py +1 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_recommend.py +1 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_search.py +29 -18
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_song.py +10 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/uv.lock +445 -536
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/requirements.txt +435 -489
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/app.py +14 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/auth.py +6 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/cache.py +13 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/security.py +9 -3
- qqmusic_api_python-0.7.2/web/src/modules/__init__.py +12 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/comment.py +5 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/login.py +9 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/mv.py +3 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/singer.py +4 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/song.py +12 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/modules/songlist.py +3 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/_helpers.py +17 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/comment.py +8 -9
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/login.py +1 -13
- qqmusic_api_python-0.7.2/web/src/routes/lyric.py +48 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/mv.py +0 -3
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/recommend.py +1 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/search.py +10 -4
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/singer.py +14 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/song.py +8 -18
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/songlist.py +0 -3
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/user.py +6 -6
- qqmusic_api_python-0.7.2/web/src/routing/adapter_registry.py +56 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/docstrings.py +7 -1
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/executor.py +9 -6
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/params.py +32 -11
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/route_types.py +3 -2
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/router_factory.py +45 -11
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_core.py +3 -3
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_docstrings.py +20 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_routes.py +7 -5
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/zensical.toml +7 -2
- 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/client.py +0 -664
- 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/_base.py +0 -351
- 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.2}/.agents/skills/pydantic/SKILL.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.agents/skills/python-standards/SKILL.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.agents/skills/uv-package-manager/SKILL.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.dockerignore +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/renovate.json +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.github/workflows/checking.yaml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.gitignore +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/.markdownlint-cli2.yaml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/CLAUDE.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/LICENSE +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/README.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/assets/qq-music.svg +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/cliff.toml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/index.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/client.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/exception.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/pagination.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/request.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/core/versioning.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/album.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/base.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/comment.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/login.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/lyric.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/mv.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/recommend.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/request.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/search.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/singer.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/song.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/songlist.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/top.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/model/user.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/album.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/comment.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/login.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/login_utils.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/lyric.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/mv.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/private_message.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/recommend.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/search.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/singer.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/song.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/songlist.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/top.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/reference/modules/user.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/credential.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/download.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/error-handling.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/login.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/start.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/docs/tutorial/web.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/download_song.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/get_all_sheets.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/phone_login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/private_message.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/qrcode_login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/examples/upload_file.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/sign.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/tripledes.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/core/exceptions.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/core/versioning.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/_validator.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/album.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/base.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/helper.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/mv.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/private_message.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/recommend.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/song.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/songlist.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/top.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/models/user.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper_utils.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login_utils.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/common.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/device.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/qimei.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/scripts/ag-1.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_login.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_login_utils.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_singer.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_songlist.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_top.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/tests/test_user.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/.gitignore +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/Dockerfile +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/README.md +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/accounts.example.toml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/config.example.toml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/docker-compose.example.yml +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/run.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/config.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/credential_store.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/deps.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/core/response.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/album.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routes/top.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/src/routing/__init__.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/conftest.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/web/tests/test_web_enums.py +0 -0
- {qqmusic_api_python-0.7.0 → qqmusic_api_python-0.7.2}/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"
|
|
@@ -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
|
* 优先在现有的测试文件中添加用例,仅在测试全新模块时才允许创建新的测试文件。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qqmusic-api-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
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'
|
|
@@ -11,31 +11,32 @@
|
|
|
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
|
-
->
|
|
20
|
-
->
|
|
18
|
+
-> ApiContext 注入环境与凭证
|
|
19
|
+
-> Session.post(...) / Session.request(...)
|
|
20
|
+
-> Request._parse_response(...)
|
|
21
|
+
-> 返回原始 dict 或 Pydantic 模型
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
### 批量并发请求
|
|
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},
|
|
@@ -66,6 +67,7 @@ class FooApi(ApiModule):
|
|
|
66
67
|
# qqmusic_api/core/client.py
|
|
67
68
|
from functools import cached_property
|
|
68
69
|
|
|
70
|
+
|
|
69
71
|
class Client:
|
|
70
72
|
@cached_property
|
|
71
73
|
def foo(self) -> "FooApi":
|
|
@@ -75,84 +77,78 @@ class Client:
|
|
|
75
77
|
|
|
76
78
|
### 添加新的请求方法
|
|
77
79
|
|
|
78
|
-
API 方法返回 `
|
|
80
|
+
API 方法返回 `BaseRequest` 描述符对象,并不立即发起请求。对于标准 CGI 风格的 RPC 请求,使用 `self._build_cgi(...)` 工厂方法构建:
|
|
79
81
|
|
|
80
82
|
```python
|
|
81
83
|
def get_detail(self, song_id: int):
|
|
82
84
|
"""获取歌曲详情."""
|
|
83
|
-
return self.
|
|
84
|
-
module="music.songDetail",
|
|
85
|
-
method="GetDetail",
|
|
86
|
-
param={"songid": song_id},
|
|
85
|
+
return self._build_cgi(
|
|
86
|
+
module="music.songDetail", # 接口所属模块
|
|
87
|
+
method="GetDetail", # 方法名
|
|
88
|
+
param={"songid": song_id}, # 业务参数
|
|
87
89
|
)
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
对于非标准 CGI 接口(如直接 GET
|
|
92
|
+
对于非标准 CGI 接口(如直接 GET 请求、获取网页或二维码),使用 `self._build_http(...)`:
|
|
91
93
|
|
|
92
94
|
```python
|
|
93
95
|
async def quick_search(self, keyword: str) -> dict[str, Any]:
|
|
94
96
|
"""快速搜索 (直接返回解析后的 JSON 数据)."""
|
|
95
|
-
resp = await self.
|
|
97
|
+
resp = await self._build_http(
|
|
96
98
|
"GET",
|
|
97
99
|
"https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg",
|
|
98
100
|
params={"key": keyword},
|
|
99
101
|
)
|
|
100
|
-
resp
|
|
101
|
-
return resp.json()["data"]
|
|
102
|
+
return resp["data"]
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
### `
|
|
105
|
-
|
|
106
|
-
| 参数 | 类型 | 说明
|
|
107
|
-
|
|
108
|
-
| `module` | `str` | 接口所属模块名
|
|
109
|
-
| `method` | `str` | 方法名
|
|
110
|
-
| `param` | `dict` | 业务参数
|
|
111
|
-
| `response_model` | `type[BaseModel]` 或 `None` | 响应模型,为 None 时返回原始 dict
|
|
112
|
-
| `comm` | `dict` 或 `None` | 附加的公共参数
|
|
113
|
-
| `override_comm` | `bool` | 为 True 时 `comm` 完全替代自动生成的参数;为 False 时合并
|
|
114
|
-
| `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证
|
|
115
|
-
| `platform` | `Platform` 或 `None` | 覆盖本次请求的平台
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
| `
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
131
|
-
| `
|
|
132
|
-
| `lazy` | `bool` | 是否延迟发送请求(用于批量并发) |
|
|
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
|
## 响应模型
|
|
@@ -176,6 +172,17 @@ class MyResponse(Response):
|
|
|
176
172
|
|
|
177
173
|
`Response` 基类配置了 `frozen=True`(不可变)和 `extra="ignore"`(忽略多余字段)。
|
|
178
174
|
|
|
175
|
+
!!! warning "Pydantic 默认值规范"
|
|
176
|
+
|
|
177
|
+
定义模型时应避免使用 `None` 作为隐式兜底默认值。如果字段可选或为空,应当使用显式的空标量,或通过 `Field(default_factory=...)` 声明:
|
|
178
|
+
```python
|
|
179
|
+
class Album(Response):
|
|
180
|
+
name: str = ""
|
|
181
|
+
publish_time: str = ""
|
|
182
|
+
# 列表必须使用 default_factory
|
|
183
|
+
singers: list[Singer] = Field(default_factory=list)
|
|
184
|
+
```
|
|
185
|
+
|
|
179
186
|
### JSONPath 字段映射
|
|
180
187
|
|
|
181
188
|
可以通过 `Field(json_schema_extra={"jsonpath": ...})` 声明字段的 JSONPath 映射路径,自动从嵌套响应中提取数据:
|
|
@@ -221,12 +228,12 @@ class Singer(Response):
|
|
|
221
228
|
|
|
222
229
|
### 需登录的接口
|
|
223
230
|
|
|
224
|
-
需要登录的接口通过 `
|
|
231
|
+
需要登录的接口通过 `_build_cgi` 的 `require_login` 参数校验凭证:
|
|
225
232
|
|
|
226
233
|
```python
|
|
227
234
|
def get_vip_info(self, *, credential: Credential | None = None):
|
|
228
235
|
"""获取 VIP 信息."""
|
|
229
|
-
return self.
|
|
236
|
+
return self._build_cgi(
|
|
230
237
|
module="VipLogin.VipLoginInter",
|
|
231
238
|
method="vip_login_base",
|
|
232
239
|
param={},
|
|
@@ -236,22 +243,24 @@ def get_vip_info(self, *, credential: Credential | None = None):
|
|
|
236
243
|
)
|
|
237
244
|
```
|
|
238
245
|
|
|
239
|
-
>
|
|
240
|
-
>
|
|
246
|
+
> 若接口需要凭证对象的属性(如 `musicid` 等)来构建请求内联参数,
|
|
247
|
+
> 仍可通过 `credential = credential or self._client.credential` 获取并显式验证其有效性。
|
|
241
248
|
|
|
242
|
-
##
|
|
249
|
+
## 连续翻页与批次刷新
|
|
243
250
|
|
|
244
251
|
### 连续翻页
|
|
245
252
|
|
|
246
|
-
通过 `
|
|
253
|
+
通过 `pager_strategy` 声明连续翻页能力,建议配合显示 Generic 标注(形如
|
|
254
|
+
`OffsetStrategy[GetSonglistDetailResponse]`)以确保静态类型检查与类型推断的准确性,并通过 `.with_extractor()`
|
|
255
|
+
链式调用绑定实体数据项的提取逻辑:
|
|
247
256
|
|
|
248
257
|
```python
|
|
249
|
-
from ..core.pagination import OffsetStrategy
|
|
258
|
+
from ..core.pagination import OffsetStrategy
|
|
250
259
|
|
|
251
260
|
|
|
252
261
|
def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
|
|
253
262
|
"""获取歌单详情."""
|
|
254
|
-
return self.
|
|
263
|
+
return self._build_cgi(
|
|
255
264
|
module="music.srfDissInfo.DissInfo",
|
|
256
265
|
method="CgiGetDiss",
|
|
257
266
|
param={
|
|
@@ -260,40 +269,38 @@ def get_detail(self, songlist_id: int, num: int = 10, page: int = 1):
|
|
|
260
269
|
"song_num": num,
|
|
261
270
|
},
|
|
262
271
|
response_model=GetSonglistDetailResponse,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
),
|
|
272
|
+
pager_strategy=OffsetStrategy[GetSonglistDetailResponse](
|
|
273
|
+
offset_key="song_begin",
|
|
274
|
+
page_size_key="song_num",
|
|
275
|
+
has_more_extractor=lambda response: bool(response.hasmore),
|
|
276
|
+
total_extractor=lambda response: response.total,
|
|
277
|
+
count_extractor=lambda response: len(response.songs),
|
|
270
278
|
),
|
|
271
|
-
)
|
|
279
|
+
).with_extractor(lambda response: response.songs)
|
|
272
280
|
```
|
|
273
281
|
|
|
274
|
-
###
|
|
282
|
+
### 批次刷新 (Batch Refresh)
|
|
275
283
|
|
|
276
|
-
|
|
284
|
+
批次刷新(Batch Refresh)是一种针对推荐或关联接口、支持游标复位与防循环重复游标的特殊游标分页,同样通过 `pager_strategy` 声明:
|
|
277
285
|
|
|
278
286
|
```python
|
|
279
|
-
from ..core.pagination import BatchRefreshStrategy
|
|
287
|
+
from ..core.pagination import BatchRefreshStrategy
|
|
288
|
+
from ..models.base import MV
|
|
280
289
|
|
|
281
290
|
|
|
282
291
|
def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
283
292
|
"""获取歌曲相关 MV."""
|
|
284
|
-
return self.
|
|
293
|
+
return self._build_cgi(
|
|
285
294
|
module="MvService.MvInfoProServer",
|
|
286
295
|
method="GetSongRelatedMv",
|
|
287
296
|
param={"songid": str(songid), "songtype": 1, "lastmvid": last_mvid or 0},
|
|
288
297
|
response_model=GetRelatedMvResponse,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
cursor=lambda response: response.mv[-1].id if response.mv else None,
|
|
294
|
-
),
|
|
298
|
+
pager_strategy=BatchRefreshStrategy[GetRelatedMvResponse](
|
|
299
|
+
refresh_key="lastmvid",
|
|
300
|
+
cursor_extractor=lambda response: response.mv[-1].id if response.mv else None,
|
|
301
|
+
has_more_extractor=lambda response: bool(response.has_more),
|
|
295
302
|
),
|
|
296
|
-
)
|
|
303
|
+
).with_extractor(lambda response: response.mv)
|
|
297
304
|
```
|
|
298
305
|
|
|
299
306
|
### 内置策略速查
|
|
@@ -304,26 +311,7 @@ def get_related_mv(self, songid: int, last_mvid: str | None = None):
|
|
|
304
311
|
| `OffsetStrategy` | 偏移量滑窗 | `offset_key` + `page_size_key` |
|
|
305
312
|
| `CursorStrategy` | 响应游标回写 | `cursor_key` |
|
|
306
313
|
| `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`。
|
|
314
|
+
| `BatchRefreshStrategy` | 批次刷新 | `refresh_key` |
|
|
327
315
|
|
|
328
316
|
## 请求签名
|
|
329
317
|
|
|
@@ -332,7 +320,7 @@ JCE 协议的响应会自动解码为 `TarsDict`。
|
|
|
332
320
|
```python
|
|
333
321
|
def get_sheet(self, mid: str):
|
|
334
322
|
"""获取曲谱."""
|
|
335
|
-
return self.
|
|
323
|
+
return self._build_cgi(
|
|
336
324
|
module="music.mir.SheetMusicSvr",
|
|
337
325
|
method="GetMoreSheetMusic",
|
|
338
326
|
param={"songMid": mid},
|
|
@@ -348,7 +336,7 @@ def get_sheet(self, mid: str):
|
|
|
348
336
|
|
|
349
337
|
```python
|
|
350
338
|
# 合并到自动生成的 comm 中(默认行为)
|
|
351
|
-
self.
|
|
339
|
+
self._build_cgi(
|
|
352
340
|
...,
|
|
353
341
|
comm={"extra_key": "value"},
|
|
354
342
|
)
|
|
@@ -357,7 +345,7 @@ self._build_request(
|
|
|
357
345
|
使用 `override_comm=True` 完全替代自动生成的参数:
|
|
358
346
|
|
|
359
347
|
```python
|
|
360
|
-
self.
|
|
348
|
+
self._build_cgi(
|
|
361
349
|
...,
|
|
362
350
|
comm={
|
|
363
351
|
"g_tk": 5381,
|
|
@@ -372,6 +360,20 @@ self._build_request(
|
|
|
372
360
|
)
|
|
373
361
|
```
|
|
374
362
|
|
|
363
|
+
## 异常处理
|
|
364
|
+
|
|
365
|
+
在抛出或处理异常时,应使用项目统一的基于领域驱动(DDD)风格的异常类(继承自 `BaseApiException` 或 `ApiException`
|
|
366
|
+
)。在包装底层异常时,必须使用原生异常链(`raise ... from exc`)保留堆栈追踪:
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
from ..core.exceptions import ApiDataError
|
|
370
|
+
|
|
371
|
+
try:
|
|
372
|
+
...
|
|
373
|
+
except KeyError as e:
|
|
374
|
+
raise ApiDataError("无法解析歌曲信息") from e
|
|
375
|
+
```
|
|
376
|
+
|
|
375
377
|
## 编写测试
|
|
376
378
|
|
|
377
379
|
测试文件放在 `tests/` 下,按模块命名(如 `test_song.py`)。
|
|
@@ -398,8 +400,15 @@ async def test_query_song(client: Client) -> None:
|
|
|
398
400
|
```python
|
|
399
401
|
@pytest.mark.parametrize("page", [1, 2])
|
|
400
402
|
async def test_general_search(client: Client, page: int) -> None:
|
|
401
|
-
"""
|
|
402
|
-
|
|
403
|
+
"""测试综合搜索翻页逻辑."""
|
|
404
|
+
try:
|
|
405
|
+
result = await client.search.general_search("周杰伦", page=page)
|
|
406
|
+
except Exception as e:
|
|
407
|
+
# 示例:优雅处理网络风控或限流 (需根据实际异常类型调整)
|
|
408
|
+
if "limit" in str(e).lower() or "risk" in str(e).lower():
|
|
409
|
+
pytest.skip(f"Triggered rate limit or risk control: {e}")
|
|
410
|
+
raise
|
|
411
|
+
|
|
403
412
|
assert result.song.items is not None
|
|
404
413
|
```
|
|
405
414
|
|
|
@@ -419,7 +428,7 @@ async def test_get_vip_info(authenticated_client: Client) -> None:
|
|
|
419
428
|
```python
|
|
420
429
|
async def test_search_paginate(client: Client) -> None:
|
|
421
430
|
"""测试搜索分页."""
|
|
422
|
-
pager = client.search.search_by_type("周杰伦", num=5).
|
|
431
|
+
pager = client.search.search_by_type("周杰伦", num=5).pager(limit=2)
|
|
423
432
|
|
|
424
433
|
assert pager.has_more() is True
|
|
425
434
|
first_page = await pager.next()
|
|
@@ -80,7 +80,7 @@ uv run mkdocs serve
|
|
|
80
80
|
|
|
81
81
|
* 注释内容包括:模块注释、类注释、函数注释、参数类型注释、返回值注释
|
|
82
82
|
* 注释风格遵循 [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings)
|
|
83
|
-
*
|
|
83
|
+
* 测试用例必须包含 **单行中文 docstring**,且内部必须使用 **英文标点符号**。
|
|
84
84
|
|
|
85
85
|
## 文档规范
|
|
86
86
|
|
|
@@ -1,4 +1,62 @@
|
|
|
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
|
+
|
|
44
|
+
## [[0.7.0](https://github.com/L-1124/QQMusicApi/compare/v0.6.9..v0.7.0)] - 2026-07-22
|
|
45
|
+
|
|
46
|
+
### Bug 修复
|
|
47
|
+
|
|
48
|
+
* fix retry import path ([75d0693](https://github.com/L-1124/QQMusicApi/commit/75d069344a07c6713b4a616ed6304ca6dc1b740a)) by [@L-1124](https://github.com/L-1124)
|
|
49
|
+
|
|
50
|
+
### 功能更新
|
|
51
|
+
|
|
52
|
+
* **(lyric)** 支持指定特殊歌曲类型(song_type)查询歌词 ([1b0aae0](https://github.com/L-1124/QQMusicApi/commit/1b0aae0db3ee6876b3a77b8d1ce3057b4b3c9cd5)) by [@L-1124](https://github.com/L-1124)
|
|
53
|
+
* **(song)** [**breaking**] 重构歌曲查询支持 SongQueryInfo 与 Web POST 批量路由 ([4cbb2c6](https://github.com/L-1124/QQMusicApi/commit/4cbb2c6e993d58db71494ce31809a144bfc33be4)) by [@L-1124](https://github.com/L-1124)
|
|
54
|
+
|
|
55
|
+
### 贡献者
|
|
56
|
+
|
|
57
|
+
* @L-1124
|
|
58
|
+
* @github-actions[bot]
|
|
59
|
+
|
|
2
60
|
## [[0.6.9](https://github.com/L-1124/QQMusicApi/compare/v0.6.8..v0.6.9)] - 2026-07-12
|
|
3
61
|
|
|
4
62
|
### Bug 修复
|