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.
Files changed (207) hide show
  1. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/AGENTS.md +6 -3
  2. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/PKG-INFO +1 -1
  3. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/coding.md +64 -68
  4. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/contributing.md +1 -9
  5. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/release-notes.md +42 -0
  6. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/client.md +46 -4
  7. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/pagination.md +6 -6
  8. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/pyproject.toml +3 -0
  9. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/__init__.py +1 -1
  10. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/__init__.py +6 -2
  11. qqmusic_api_python-0.7.2/qqmusic_api/core/api_context.py +227 -0
  12. qqmusic_api_python-0.7.2/qqmusic_api/core/client.py +531 -0
  13. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/pagination.py +240 -24
  14. qqmusic_api_python-0.7.2/qqmusic_api/core/request.py +371 -0
  15. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/search.py +174 -0
  16. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/singer.py +28 -1
  17. qqmusic_api_python-0.7.2/qqmusic_api/modules/_base.py +231 -0
  18. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/album.py +5 -5
  19. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/comment.py +7 -7
  20. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper.py +2 -2
  21. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login.py +38 -32
  22. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/lyric.py +6 -7
  23. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/mv.py +3 -3
  24. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/private_message.py +18 -19
  25. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/recommend.py +5 -5
  26. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/search.py +32 -22
  27. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/singer.py +32 -11
  28. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/song.py +14 -14
  29. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/songlist.py +5 -5
  30. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/top.py +2 -2
  31. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/user.py +19 -19
  32. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/conftest.py +5 -2
  33. qqmusic_api_python-0.7.2/tests/test_api_context.py +356 -0
  34. qqmusic_api_python-0.7.2/tests/test_client.py +335 -0
  35. qqmusic_api_python-0.7.2/tests/test_pagination.py +490 -0
  36. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_private_message.py +1 -1
  37. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_search.py +4 -4
  38. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/uv.lock +34 -34
  39. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/requirements.txt +435 -489
  40. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/singer.py +2 -1
  41. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/_helpers.py +7 -0
  42. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/search.py +10 -4
  43. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/singer.py +14 -3
  44. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/router_factory.py +3 -3
  45. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/zensical.toml +0 -1
  46. qqmusic_api_python-0.7.1/qqmusic_api/core/client.py +0 -626
  47. qqmusic_api_python-0.7.1/qqmusic_api/core/request.py +0 -220
  48. qqmusic_api_python-0.7.1/qqmusic_api/modules/_base.py +0 -225
  49. qqmusic_api_python-0.7.1/tests/test_pagination.py +0 -401
  50. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.agents/skills/pydantic/SKILL.md +0 -0
  51. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.agents/skills/python-standards/SKILL.md +0 -0
  52. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.agents/skills/uv-package-manager/SKILL.md +0 -0
  53. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.dockerignore +0 -0
  54. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
  55. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  56. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
  57. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  58. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/renovate.json +0 -0
  59. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/checking.yaml +0 -0
  60. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/docs.yml +0 -0
  61. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/release.yml +0 -0
  62. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.github/workflows/testing.yml +0 -0
  63. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.gitignore +0 -0
  64. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/.markdownlint-cli2.yaml +0 -0
  65. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/CLAUDE.md +0 -0
  66. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/LICENSE +0 -0
  67. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/README.md +0 -0
  68. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/assets/qq-music.svg +0 -0
  69. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/cliff.toml +0 -0
  70. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/index.md +0 -0
  71. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/client.md +0 -0
  72. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/exception.md +0 -0
  73. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/pagination.md +0 -0
  74. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/request.md +0 -0
  75. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/core/versioning.md +0 -0
  76. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/album.md +0 -0
  77. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/base.md +0 -0
  78. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/comment.md +0 -0
  79. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/helper.md +0 -0
  80. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/login.md +0 -0
  81. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/lyric.md +0 -0
  82. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/mv.md +0 -0
  83. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/private_message.md +0 -0
  84. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/recommend.md +0 -0
  85. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/request.md +0 -0
  86. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/search.md +0 -0
  87. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/singer.md +0 -0
  88. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/song.md +0 -0
  89. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/songlist.md +0 -0
  90. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/top.md +0 -0
  91. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/model/user.md +0 -0
  92. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/album.md +0 -0
  93. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/comment.md +0 -0
  94. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/helper.md +0 -0
  95. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/helper_utils.md +0 -0
  96. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/login.md +0 -0
  97. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/login_utils.md +0 -0
  98. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/lyric.md +0 -0
  99. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/mv.md +0 -0
  100. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/private_message.md +0 -0
  101. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/recommend.md +0 -0
  102. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/search.md +0 -0
  103. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/singer.md +0 -0
  104. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/song.md +0 -0
  105. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/songlist.md +0 -0
  106. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/top.md +0 -0
  107. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/reference/modules/user.md +0 -0
  108. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/credential.md +0 -0
  109. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/download.md +0 -0
  110. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/error-handling.md +0 -0
  111. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/login.md +0 -0
  112. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/start.md +0 -0
  113. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/docs/tutorial/web.md +0 -0
  114. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/download_song.py +0 -0
  115. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/get_all_sheets.py +0 -0
  116. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/phone_login.py +0 -0
  117. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/private_message.py +0 -0
  118. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/qrcode_login.py +0 -0
  119. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/examples/upload_file.py +0 -0
  120. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/prek.toml +0 -0
  121. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/__init__.py +0 -0
  122. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/sign.py +0 -0
  123. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/algorithms/tripledes.py +0 -0
  124. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/exceptions.py +0 -0
  125. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/core/versioning.py +0 -0
  126. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/__init__.py +0 -0
  127. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/_validator.py +0 -0
  128. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/album.py +0 -0
  129. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/base.py +0 -0
  130. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/comment.py +0 -0
  131. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/helper.py +0 -0
  132. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/login.py +0 -0
  133. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/lyric.py +0 -0
  134. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/mv.py +0 -0
  135. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/private_message.py +0 -0
  136. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/recommend.py +0 -0
  137. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/request.py +0 -0
  138. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/song.py +0 -0
  139. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/songlist.py +0 -0
  140. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/top.py +0 -0
  141. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/models/user.py +0 -0
  142. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/__init__.py +0 -0
  143. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/helper_utils.py +0 -0
  144. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/modules/login_utils.py +0 -0
  145. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/__init__.py +0 -0
  146. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/common.py +0 -0
  147. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/device.py +0 -0
  148. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/mqtt.py +0 -0
  149. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/qqmusic_api/utils/qimei.py +0 -0
  150. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/scripts/ag-1.py +0 -0
  151. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_album.py +0 -0
  152. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_comment.py +0 -0
  153. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_login.py +0 -0
  154. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_login_utils.py +0 -0
  155. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_lyric.py +0 -0
  156. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_mv.py +0 -0
  157. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_recommend.py +0 -0
  158. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_singer.py +0 -0
  159. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_song.py +0 -0
  160. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_songlist.py +0 -0
  161. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_top.py +0 -0
  162. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/tests/test_user.py +0 -0
  163. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/.gitignore +0 -0
  164. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/Dockerfile +0 -0
  165. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/README.md +0 -0
  166. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/accounts.example.toml +0 -0
  167. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/config.example.toml +0 -0
  168. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/docker-compose.example.yml +0 -0
  169. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/run.py +0 -0
  170. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/app.py +0 -0
  171. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/__init__.py +0 -0
  172. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/auth.py +0 -0
  173. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/cache.py +0 -0
  174. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/config.py +0 -0
  175. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/credential_store.py +0 -0
  176. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/deps.py +0 -0
  177. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/response.py +0 -0
  178. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/core/security.py +0 -0
  179. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/__init__.py +0 -0
  180. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/comment.py +0 -0
  181. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/login.py +0 -0
  182. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/mv.py +0 -0
  183. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/song.py +0 -0
  184. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/modules/songlist.py +0 -0
  185. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/__init__.py +0 -0
  186. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/album.py +0 -0
  187. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/comment.py +0 -0
  188. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/login.py +0 -0
  189. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/lyric.py +0 -0
  190. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/mv.py +0 -0
  191. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/recommend.py +0 -0
  192. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/song.py +0 -0
  193. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/songlist.py +0 -0
  194. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/top.py +0 -0
  195. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routes/user.py +0 -0
  196. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/__init__.py +0 -0
  197. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/adapter_registry.py +0 -0
  198. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/docstrings.py +0 -0
  199. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/executor.py +0 -0
  200. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/params.py +0 -0
  201. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/src/routing/route_types.py +0 -0
  202. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/conftest.py +0 -0
  203. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_core.py +0 -0
  204. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_docstrings.py +0 -0
  205. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_enums.py +0 -0
  206. {qqmusic_api_python-0.7.1 → qqmusic_api_python-0.7.2}/web/tests/test_web_route_validation.py +0 -0
  207. {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
- * **专注 Modules 层**:测试重心放在 `modules` 层,将其视为黑盒。采用基于数据驱动(`@pytest.mark.parametrize`)的函数式测试方法,将输入参数与期望结果的特征断言解耦。
27
- * **真实网络请求(No Mock)**:禁止 Mock 底层网络请求或核心组装逻辑。必须直接与真实的 QQ 音乐 API 交互,以验证接口连通性、参数拼装和数据模型(Models)解析的正确性。
28
- * **优雅处理限流(Rate Limit)**:由于采用真实网络请求,当触发上游 API 的风控或频率限制异常时,必须使用自定义装饰器捕获该异常并调用 `pytest.skip()` 安全跳过,严禁因此导致测试失败。
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.1
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/
@@ -11,12 +11,13 @@
11
11
 
12
12
  ```text
13
13
  模块方法
14
- -> self._build_request(...)
15
- -> Request
14
+ -> self._build_cgi(...) / self._build_http(...)
15
+ -> BaseRequest 描述符
16
16
  -> await request
17
17
  -> Client.execute(request)
18
- -> Client.request_api(...)
19
- -> Client._build_result(...)
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._build_request(...)
28
- -> Request 列表
28
+ -> self._build_cgi(...)
29
+ -> BaseRequest 描述符列表
29
30
  -> Client.gather(requests)
30
- -> 按协议、平台、公共参数和凭证分组
31
+ -> 按协议、平台、公共参数和凭证配置键自动分组
31
32
  -> 每组按 batch_size 拆分为批量请求
32
- -> 依次调用 Client.request_api(..., lazy=True) 生成响应任务
33
- -> 使用客户端内部的 multiplexed AsyncSession 并发执行这些任务(self._session.gather)
34
- -> 按 req_n 解析每个响应项
35
- -> 按输入顺序返回结果
33
+ -> 依次发起合并的多参 CGI 请求(req_0, req_1...)
34
+ -> 使用客户端内部的 Session 并发执行这些任务(self._session.gather)
35
+ -> 统一解包解析每个响应项
36
+ -> 按输入顺序返回结果列表
36
37
  ```
37
38
 
38
- `gather` 的分组边界由 `Request._group_key` 决定。只有协议类型、显式平台、公共参数和凭证相同的请求才会合并到同一个批量请求中。
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._build_request(
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 方法返回 `Request` 对象,不直接发起请求。使用 `self._build_request(...)` 工厂方法构建:
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._build_request(
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 请求),使用 `self._request(...)`:
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._request(
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.raise_for_status()
102
- return resp.json()["data"]
102
+ return resp["data"]
103
103
  ```
104
104
 
105
- ### `_build_request` 参数说明
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
- | `pager_strategy` | `PagerStrategy` 或 `None` | 分页策略,提供后返回 `PaginatedRequest`;可链式调用 `.with_extractor()` 提升为 `ItemPaginatedRequest` |
120
- | `refresh_strategy` | `RefresherStrategy` 或 `None` | 换一批策略,提供后返回 `RefreshableRequest`;可链式调用 `.with_extractor()` 提升为 `ItemRefreshableRequest` |
121
-
122
- ### `client.request` 参数说明
123
-
124
- `client.request` 是底层 HTTP 请求方法,自动装配凭证 Cookies 和平台 User-Agent:
125
-
126
- | 参数 | 类型 | 说明 |
127
- |--------------|------------------------|----------------------------------------------------------------------------------:|
128
- | `method` | `str` | HTTP 方法,如 `"GET"`、`"POST"` |
129
- | `url` | `str` | 请求地址 |
130
- | `credential` | `Credential` 或 `None` | 覆盖本次请求的凭证,默认使用客户端凭证 |
131
- | `platform` | `Platform` 或 `None` | 覆盖本次请求的平台,默认使用客户端平台 |
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
- `client.request` 返回的是原始 `niquests.Response` 对象,需要手动解析响应。而 `_build_request` 返回的 `Request` 对象支持 `await`,会自动完成响应验证和模型解析。
136
+ `_build_cgi` 返回 `CgiRequest`,`_build_http` 返回 `HttpRequest`,两者均继承自 `BaseRequest`。它们都支持直接被 `await` 以触发网络请求并自动完成响应验证和模型解析。
138
137
 
139
138
  常见用法:
140
139
 
141
140
  ```python
142
141
  # GET 请求
143
- resp = await client.request("GET", "https://example.com/api", params={"key": "value"})
142
+ req = self._build_http("GET", "https://example.com/api", params={"key": "value"})
144
143
 
145
144
  # POST JSON
146
- resp = await client.request("POST", "https://example.com/api", json={"key": "value"})
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
- resp = await client.request("GET", "https://example.com/api", credential=my_credential)
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
- 需要登录的接口通过 `_build_request` 的 `require_login` 参数校验凭证:
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._build_request(
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
- > 仍可显式调用 `_require_login` 获取凭证对象。
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._build_request(
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._build_request(
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._build_request(
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._build_request(
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._build_request(
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
- * 测试用例必须包含**单行中文 docstring**,且内部必须使用**英文标点符号**。
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
- | 平台 | `Platform` 值 | 说明 |
96
- | ------ | -------------- | ------ |
137
+ | 平台 | `Platform` 值 | 说明 |
138
+ |---------|--------------------|----------------------|
97
139
  | Android | `Platform.ANDROID` | 默认,大部分接口使用 |
98
- | Desktop | `Platform.DESKTOP` | QQ 音乐桌面端 |
99
- | Web | `Platform.WEB` | QQ 音乐网页端 |
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
- * **`PaginatedRequest`**:具备连续翻页与批次刷新能力的请求描述符。既可直接 `await` 获取首批响应,也可通过 `.pager()`
5
+ * **`PaginatedCgiRequest`**:具备连续翻页与批次刷新能力的 CGI 请求描述符。既可直接 `await` 获取首批响应,也可通过 `.pager()`
6
6
  手动按需步进,或使用 `.paginate()`、`.collect()` 和 `async for` 进行流式与批量遍历。
7
- * **`ItemPaginatedRequest`**:具备数据项提取能力的分页扩展类。除具备通用分页方法外,还通过 `.iter_items()` 与
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 返回的请求对象是原生的 `PaginatedRequest`(即 API 层没有预设数据项提取器),你仍然可以通过
153
- `.with_extractor()` 动态注入一个提取逻辑。这会将请求无缝转换为具备跨页提取能力的 `ItemPaginatedRequest`。
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 返回原生的 PaginatedRequest
164
+ # 这个 API 返回原生的 PaginatedCgiRequest
165
165
  req = client.search.general_search("周杰伦")
166
166
 
167
167
  # 动态绑定 extractor
168
- # 此时 item_req 类型变为 ItemPaginatedRequest
168
+ # 此时 item_req 类型变为 ItemPaginatedCgiRequest
169
169
  item_req = req.with_extractor(lambda r: r.song.items if r.song else [])
170
170
 
171
171
  # 现在你可以非常自然地跨页迭代数据项了!
@@ -115,6 +115,9 @@ addopts = ["-rxXs"]
115
115
  pythonpath = ["."]
116
116
  testpaths = ["tests"]
117
117
  asyncio_mode = "auto"
118
+ markers = [
119
+ "core: Core 层单元测试, 使用桩数据驱动, 不发起真实网络请求",
120
+ ]
118
121
 
119
122
  [tool.ruff]
120
123
  line-length = 120
@@ -22,7 +22,7 @@ from .core.exceptions import (
22
22
  from .core.versioning import Platform
23
23
  from .models.request import Credential
24
24
 
25
- __version__ = "0.7.1"
25
+ __version__ = "0.7.2"
26
26
 
27
27
  __all__ = [
28
28
  "ApiDataError",
@@ -19,7 +19,7 @@ from .exceptions import (
19
19
  NetworkError,
20
20
  RatelimitedError,
21
21
  )
22
- from .request import Request
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
  ]