simcord 2.2.0__tar.gz → 2.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. {simcord-2.2.0 → simcord-2.2.1}/CHANGELOG.md +7 -0
  2. {simcord-2.2.0 → simcord-2.2.1}/PKG-INFO +1 -1
  3. {simcord-2.2.0 → simcord-2.2.1}/docs/concepts.md +4 -1
  4. {simcord-2.2.0 → simcord-2.2.1}/pyproject.toml +1 -1
  5. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/_dpy_internals.py +17 -1
  6. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_settlement_contract.py +47 -0
  7. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_dpy_internals.py +72 -0
  8. {simcord-2.2.0 → simcord-2.2.1}/uv.lock +1 -1
  9. {simcord-2.2.0 → simcord-2.2.1}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  10. {simcord-2.2.0 → simcord-2.2.1}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  11. {simcord-2.2.0 → simcord-2.2.1}/.github/ISSUE_TEMPLATE/parity-gap.md +0 -0
  12. {simcord-2.2.0 → simcord-2.2.1}/.github/dependabot.yml +0 -0
  13. {simcord-2.2.0 → simcord-2.2.1}/.github/workflows/ci.yml +0 -0
  14. {simcord-2.2.0 → simcord-2.2.1}/.github/workflows/dpy-master.yml +0 -0
  15. {simcord-2.2.0 → simcord-2.2.1}/.github/workflows/release.yml +0 -0
  16. {simcord-2.2.0 → simcord-2.2.1}/.github/workflows/verify.yml +0 -0
  17. {simcord-2.2.0 → simcord-2.2.1}/.gitignore +0 -0
  18. {simcord-2.2.0 → simcord-2.2.1}/.pre-commit-config.yaml +0 -0
  19. {simcord-2.2.0 → simcord-2.2.1}/.python-version +0 -0
  20. {simcord-2.2.0 → simcord-2.2.1}/.readthedocs.yaml +0 -0
  21. {simcord-2.2.0 → simcord-2.2.1}/CODE_OF_CONDUCT.md +0 -0
  22. {simcord-2.2.0 → simcord-2.2.1}/CONTRIBUTING.md +0 -0
  23. {simcord-2.2.0 → simcord-2.2.1}/LICENSE +0 -0
  24. {simcord-2.2.0 → simcord-2.2.1}/README.md +0 -0
  25. {simcord-2.2.0 → simcord-2.2.1}/SECURITY.md +0 -0
  26. {simcord-2.2.0 → simcord-2.2.1}/benchmarks/_helpers.py +0 -0
  27. {simcord-2.2.0 → simcord-2.2.1}/benchmarks/test_dispatch_benchmarks.py +0 -0
  28. {simcord-2.2.0 → simcord-2.2.1}/benchmarks/test_perf_guards.py +0 -0
  29. {simcord-2.2.0 → simcord-2.2.1}/changes/README.md +0 -0
  30. {simcord-2.2.0 → simcord-2.2.1}/docs/api.md +0 -0
  31. {simcord-2.2.0 → simcord-2.2.1}/docs/architecture.md +0 -0
  32. {simcord-2.2.0 → simcord-2.2.1}/docs/assets/simcord-terminal-demo.gif +0 -0
  33. {simcord-2.2.0 → simcord-2.2.1}/docs/assets/simcord-transparent.webp +0 -0
  34. {simcord-2.2.0 → simcord-2.2.1}/docs/assets/social-card.png +0 -0
  35. {simcord-2.2.0 → simcord-2.2.1}/docs/changelog.md +0 -0
  36. {simcord-2.2.0 → simcord-2.2.1}/docs/cookbook.md +0 -0
  37. {simcord-2.2.0 → simcord-2.2.1}/docs/faq.md +0 -0
  38. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/ai-coding-agents.md +0 -0
  39. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/components.md +0 -0
  40. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/diagnostics.md +0 -0
  41. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/fixtures.md +0 -0
  42. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/intents.md +0 -0
  43. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/interactions.md +0 -0
  44. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/messages.md +0 -0
  45. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/mocks-vs-simulation.md +0 -0
  46. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/moderation-and-guild.md +0 -0
  47. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/permissions.md +0 -0
  48. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/preview.md +0 -0
  49. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/test-without-token.md +0 -0
  50. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/testing-discord-py-bots.md +0 -0
  51. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/testing-slash-commands.md +0 -0
  52. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/threads-reactions-dms.md +0 -0
  53. {simcord-2.2.0 → simcord-2.2.1}/docs/guides/time-control.md +0 -0
  54. {simcord-2.2.0 → simcord-2.2.1}/docs/index.md +0 -0
  55. {simcord-2.2.0 → simcord-2.2.1}/docs/installation.md +0 -0
  56. {simcord-2.2.0 → simcord-2.2.1}/docs/llms.txt +0 -0
  57. {simcord-2.2.0 → simcord-2.2.1}/docs/parity-matrix.md +0 -0
  58. {simcord-2.2.0 → simcord-2.2.1}/docs/performance.md +0 -0
  59. {simcord-2.2.0 → simcord-2.2.1}/docs/quickstart.md +0 -0
  60. {simcord-2.2.0 → simcord-2.2.1}/docs/robots.txt +0 -0
  61. {simcord-2.2.0 → simcord-2.2.1}/docs/stability.md +0 -0
  62. {simcord-2.2.0 → simcord-2.2.1}/examples/README.md +0 -0
  63. {simcord-2.2.0 → simcord-2.2.1}/examples/__init__.py +0 -0
  64. {simcord-2.2.0 → simcord-2.2.1}/examples/bot.py +0 -0
  65. {simcord-2.2.0 → simcord-2.2.1}/examples/conftest.py +0 -0
  66. {simcord-2.2.0 → simcord-2.2.1}/examples/preview_example.py +0 -0
  67. {simcord-2.2.0 → simcord-2.2.1}/examples/test_bot.py +0 -0
  68. {simcord-2.2.0 → simcord-2.2.1}/mkdocs.yml +0 -0
  69. {simcord-2.2.0 → simcord-2.2.1}/overrides/main.html +0 -0
  70. {simcord-2.2.0 → simcord-2.2.1}/scripts/capture_visual_reference.py +0 -0
  71. {simcord-2.2.0 → simcord-2.2.1}/scripts/check_discoverability.py +0 -0
  72. {simcord-2.2.0 → simcord-2.2.1}/scripts/check_package.py +0 -0
  73. {simcord-2.2.0 → simcord-2.2.1}/scripts/compare_visual_reference.py +0 -0
  74. {simcord-2.2.0 → simcord-2.2.1}/scripts/discord_reference_bot.py +0 -0
  75. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/__init__.py +0 -0
  76. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/actors.py +0 -0
  77. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/asserts.py +0 -0
  78. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/__init__.py +0 -0
  79. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/access.py +0 -0
  80. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/cdn.py +0 -0
  81. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/errors.py +0 -0
  82. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/__init__.py +0 -0
  83. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/auditlog.py +0 -0
  84. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/automod.py +0 -0
  85. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/channel.py +0 -0
  86. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/expression.py +0 -0
  87. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/guild.py +0 -0
  88. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/interaction.py +0 -0
  89. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/invite.py +0 -0
  90. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/member.py +0 -0
  91. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/message.py +0 -0
  92. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/role.py +0 -0
  93. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/scheduled_event.py +0 -0
  94. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/stage_instance.py +0 -0
  95. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/user.py +0 -0
  96. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/voice.py +0 -0
  97. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/models/webhook.py +0 -0
  98. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/__init__.py +0 -0
  99. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/audit.py +0 -0
  100. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/base.py +0 -0
  101. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/channels.py +0 -0
  102. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/commands.py +0 -0
  103. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/expressions.py +0 -0
  104. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/guilds.py +0 -0
  105. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/invites.py +0 -0
  106. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/messages.py +0 -0
  107. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/permissions.py +0 -0
  108. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/polls.py +0 -0
  109. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/reactions.py +0 -0
  110. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/scheduled_events.py +0 -0
  111. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/voice.py +0 -0
  112. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/ops/webhooks.py +0 -0
  113. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/permissions.py +0 -0
  114. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/serializers.py +0 -0
  115. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/backend/state.py +0 -0
  116. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/builders.py +0 -0
  117. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/components.py +0 -0
  118. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/enums.py +0 -0
  119. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/env.py +0 -0
  120. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/gateway.py +0 -0
  121. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/__init__.py +0 -0
  122. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/_helpers.py +0 -0
  123. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/client.py +0 -0
  124. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/router.py +0 -0
  125. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/__init__.py +0 -0
  126. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/application.py +0 -0
  127. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/auditlogs.py +0 -0
  128. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/automod.py +0 -0
  129. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/channels.py +0 -0
  130. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/commands.py +0 -0
  131. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/expressions.py +0 -0
  132. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/guilds.py +0 -0
  133. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/interactions.py +0 -0
  134. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/invites.py +0 -0
  135. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/messages.py +0 -0
  136. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/reactions.py +0 -0
  137. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/scheduled_events.py +0 -0
  138. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/http/routes/stage_instances.py +0 -0
  139. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/intents.py +0 -0
  140. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/interactions.py +0 -0
  141. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/parity.py +0 -0
  142. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/__init__.py +0 -0
  143. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_actions.py +0 -0
  144. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_assets.py +0 -0
  145. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_capture.py +0 -0
  146. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_markdown.py +0 -0
  147. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_media.py +0 -0
  148. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_pages.py +0 -0
  149. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_server.py +0 -0
  150. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/_snapshot.py +0 -0
  151. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/protocol.schema.json +0 -0
  152. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/app.js +0 -0
  153. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/components.js +0 -0
  154. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Arabic.txt +0 -0
  155. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/OFL-1.1-Noto-CJK.txt +0 -0
  156. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Devanagari.txt +0 -0
  157. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Emoji.txt +0 -0
  158. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Hebrew.txt +0 -0
  159. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/OFL-1.1-Noto.txt +0 -0
  160. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/manifest.json +0 -0
  161. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-color-emoji-v2.051.ttf +0 -0
  162. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-sans-arabic-v2.012.ttf +0 -0
  163. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-sans-devanagari-v2.007.ttf +0 -0
  164. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-sans-hebrew-v3.001.ttf +0 -0
  165. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-sans-latin-italic-v2.015.ttf +0 -0
  166. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-sans-latin-v2.015.ttf +0 -0
  167. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-sans-mono-v2.014.ttf +0 -0
  168. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/fonts/noto-sans-sc-v2.004.ttf +0 -0
  169. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/index.html +0 -0
  170. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/preview/static/preview.css +0 -0
  171. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/py.typed +0 -0
  172. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/pytest_plugin.py +0 -0
  173. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/results.py +0 -0
  174. {simcord-2.2.0 → simcord-2.2.1}/src/simcord/types.py +0 -0
  175. {simcord-2.2.0 → simcord-2.2.1}/tests/conformance/_world.py +0 -0
  176. {simcord-2.2.0 → simcord-2.2.1}/tests/conformance/test_field_honesty.py +0 -0
  177. {simcord-2.2.0 → simcord-2.2.1}/tests/conformance/test_field_honesty_fuzz.py +0 -0
  178. {simcord-2.2.0 → simcord-2.2.1}/tests/conformance/test_route_coverage.py +0 -0
  179. {simcord-2.2.0 → simcord-2.2.1}/tests/conformance/test_serializers.py +0 -0
  180. {simcord-2.2.0 → simcord-2.2.1}/tests/conftest.py +0 -0
  181. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/__init__.py +0 -0
  182. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/preview/__init__.py +0 -0
  183. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/preview/catalog.py +0 -0
  184. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/preview/coverage.json +0 -0
  185. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/preview/measurements.json +0 -0
  186. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/preview/profiles.json +0 -0
  187. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/preview/states.json +0 -0
  188. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/sample_bot/__init__.py +0 -0
  189. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/sample_bot/events.py +0 -0
  190. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/sample_bot/general.py +0 -0
  191. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/sample_bot/interactions.py +0 -0
  192. {simcord-2.2.0 → simcord-2.2.1}/tests/fixtures/sample_bot/moderation.py +0 -0
  193. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/preview_helpers.py +0 -0
  194. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_app_commands.py +0 -0
  195. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_application_emojis.py +0 -0
  196. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_asserts.py +0 -0
  197. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_audit_logs.py +0 -0
  198. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_automod.py +0 -0
  199. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_bot_users_and_webhooks.py +0 -0
  200. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_channels.py +0 -0
  201. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_command_permissions.py +0 -0
  202. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_common_bot_sweep.py +0 -0
  203. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_components_v2_interactions.py +0 -0
  204. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_components_v2_messages.py +0 -0
  205. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_coverage_sweep.py +0 -0
  206. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_diagnostics.py +0 -0
  207. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_edge_cases.py +0 -0
  208. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_error_capture_and_edits.py +0 -0
  209. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_expressions.py +0 -0
  210. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_forum.py +0 -0
  211. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_gateway_and_automod_exempt.py +0 -0
  212. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_guilds.py +0 -0
  213. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_intents.py +0 -0
  214. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_interaction_followups.py +0 -0
  215. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_interactions.py +0 -0
  216. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_invites.py +0 -0
  217. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_lifecycle.py +0 -0
  218. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_members.py +0 -0
  219. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_message_routes.py +0 -0
  220. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_messages.py +0 -0
  221. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_misc_branches.py +0 -0
  222. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_moderation.py +0 -0
  223. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_polls.py +0 -0
  224. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview.py +0 -0
  225. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_additional.py +0 -0
  226. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_capture.py +0 -0
  227. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_capture_dx.py +0 -0
  228. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_coverage.py +0 -0
  229. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_dx.py +0 -0
  230. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_edges.py +0 -0
  231. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_fixes.py +0 -0
  232. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_fonts.py +0 -0
  233. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_preview_security.py +0 -0
  234. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_reactions.py +0 -0
  235. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_roles_and_ordering.py +0 -0
  236. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_scheduled_events.py +0 -0
  237. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_setup_hook_suspension.py +0 -0
  238. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_sharding.py +0 -0
  239. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_stage_instances.py +0 -0
  240. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_threads.py +0 -0
  241. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_time.py +0 -0
  242. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_transport_edges.py +0 -0
  243. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_voice_state.py +0 -0
  244. {simcord-2.2.0 → simcord-2.2.1}/tests/integration/test_world_helpers.py +0 -0
  245. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_assert_helpers.py +0 -0
  246. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_backend_lookup_errors.py +0 -0
  247. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_components_v2_validation.py +0 -0
  248. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_errors.py +0 -0
  249. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_intent_rules.py +0 -0
  250. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_lazy_import.py +0 -0
  251. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_parity.py +0 -0
  252. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_parity_tooling.py +0 -0
  253. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_permission_validators.py +0 -0
  254. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_permissions.py +0 -0
  255. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_pytest_plugin.py +0 -0
  256. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_router.py +0 -0
  257. {simcord-2.2.0 → simcord-2.2.1}/tests/unit/test_version.py +0 -0
@@ -4,6 +4,13 @@ This changelog is generated with [towncrier](https://towncrier.readthedocs.io/).
4
4
 
5
5
  <!-- towncrier release notes start -->
6
6
 
7
+ ## 2.2.1 (2026-09-24)
8
+
9
+ ### Bug fixes
10
+
11
+ - Fixed View/Modal expiry recognition for views that never reach the store's dispatch tables — a view whose dispatchable items are all `DynamicItem`s (e.g. a `LayoutView` built only from patterned custom IDs) registered a live expiry task that settlement never recognized, so its `timeout=` blocked actor operations and fired on the wall clock. Expiry tasks are now identified by the coroutine they run (`BaseView.__timeout_task_impl`), which covers every registered view regardless of how the store indexes it.
12
+
13
+
7
14
  ## 2.2.0 (2026-09-23)
8
15
 
9
16
  ### Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: simcord
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Offline discord.py bot testing with pytest. No network, token, or test server.
5
5
  Project-URL: Homepage, https://github.com/SilentHacks/simcord
6
6
  Project-URL: Documentation, https://simcord.readthedocs.io/
@@ -95,13 +95,16 @@ assert channel.last_message.content == "Pong!" # the reply is already here
95
95
 
96
96
  Bot-owned work stays bot-owned across parking, timeout, cancellation, later actions, startup,
97
97
  and restart. Recognized waits — `Client.wait_for` (including its `timeout=`), View/Modal
98
- completion, store-registered View/Modal expiry timers, `discord.ext.tasks` loop intervals,
98
+ completion, View/Modal expiry timers, `discord.ext.tasks` loop intervals,
99
99
  composed `gather`/`shield`/`wait`/`TaskGroup`, explicit `env.external_wait(...)`, and verified
100
100
  far-future sleeps — may remain. A timer whose only job is resuming a recognized wait — a
101
101
  `wait_for` deadline on a listener, a View/Modal expiry, a loop interval, a wake-up timer
102
102
  scheduled inside `external_wait` — is virtual: it fires only via `env.advance_time()` or the
103
103
  awaited input, never the wall clock. Verified far-future sleeps keep their real fallback, and
104
104
  a View sent with no dispatchable items never registers (its expiry keeps real-time behavior).
105
+ Views whose dispatchable items are all `DynamicItem`s are covered too — expiry is identified
106
+ from the task's coroutine, not the store's dispatch table; `view.wait()` on such a view is not
107
+ recognized, so `await env.external_wait(view.wait(), reason="...")` if a bot awaits one.
105
108
  Unknown waits remain active and time out. Use a reasoned, scoped declaration for external
106
109
  input:
107
110
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "simcord"
7
- version = "2.2.0"
7
+ version = "2.2.1"
8
8
  description = "Offline discord.py bot testing with pytest. No network, token, or test server."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -77,6 +77,13 @@ def verify() -> None:
77
77
  for suffix in ("__stopped", "__timeout_task"):
78
78
  if not any(name.endswith(suffix) for name in vars(view_probe)): # pragma: no cover
79
79
  problems.append(f"BaseView.{suffix}")
80
+ impl_probe = getattr(_view.BaseView, "_BaseView__timeout_task_impl", None)
81
+ if (
82
+ impl_probe is None
83
+ or not asyncio.iscoroutinefunction(impl_probe)
84
+ or "self" not in impl_probe.__code__.co_varnames
85
+ ): # pragma: no cover
86
+ problems.append("BaseView._BaseView__timeout_task_impl coroutine")
80
87
  if not hasattr(asyncio.timeouts.Timeout(0.0), "_task"): # pragma: no cover
81
88
  problems.append("Timeout._task")
82
89
 
@@ -197,7 +204,16 @@ def is_wakeup_callback(callback: Any, args: tuple[Any, ...], waiter: Any, task:
197
204
 
198
205
 
199
206
  def _view_timeout_task_identity(client: discord.Client, task: asyncio.Task[Any]) -> bool:
200
- """True when task is some store-registered View/Modal's expiry task."""
207
+ """True when task is a registered View/Modal's expiry task."""
208
+ coro = task.get_coro()
209
+ impl = getattr(_view.BaseView, "_BaseView__timeout_task_impl", None)
210
+ if impl is not None and getattr(coro, "cr_code", None) is getattr(impl, "__code__", None):
211
+ owner = getattr(getattr(coro, "cr_frame", None), "f_locals", {}).get("self")
212
+ if isinstance(owner, _view.BaseView):
213
+ for name, value in vars(owner).items():
214
+ if name.endswith("__stopped") and isinstance(value, asyncio.Future) and value.done():
215
+ return False
216
+ return True
201
217
  store = getattr(get_state(client), "_view_store", None)
202
218
  if store is None:
203
219
  return False
@@ -1074,6 +1074,53 @@ async def test_view_button_click_before_expiry_is_joined():
1074
1074
  assert timeouts == 0
1075
1075
 
1076
1076
 
1077
+ @pytest.mark.asyncio
1078
+ async def test_fully_dynamic_view_expiry_is_virtual_only():
1079
+ """A LayoutView whose only dispatchable item is a DynamicItem lands solely
1080
+ in the store's pattern table — no ``_views`` entry — yet its expiry task is
1081
+ still a recognized wait: the verb returns promptly, the real clock never
1082
+ fires it, and advance_time() runs on_timeout exactly once."""
1083
+ bot = create_bot()
1084
+ timeouts = 0
1085
+
1086
+ class ApproveButton(discord.ui.DynamicItem[discord.ui.Button], template=r"approve:\d+"):
1087
+ def __init__(self) -> None:
1088
+ super().__init__(discord.ui.Button(label="Approve", custom_id="approve:1"))
1089
+
1090
+ async def callback(self, interaction: discord.Interaction) -> None:
1091
+ await interaction.response.send_message("approved")
1092
+
1093
+ class OfferView(discord.ui.LayoutView):
1094
+ def __init__(self) -> None:
1095
+ super().__init__(timeout=0.3)
1096
+ row = discord.ui.ActionRow()
1097
+ row.add_item(ApproveButton())
1098
+ self.add_item(row)
1099
+
1100
+ async def on_timeout(self) -> None:
1101
+ nonlocal timeouts
1102
+ timeouts += 1
1103
+
1104
+ @bot.tree.command(name="dynamic-offer")
1105
+ async def dynamic_offer(interaction: discord.Interaction) -> None:
1106
+ await interaction.response.send_message(view=OfferView())
1107
+
1108
+ async with simcord.run(bot, settle_timeout=1.0) as env:
1109
+ guild = env.create_guild()
1110
+ alice = guild.add_member(env.create_user("alice"))
1111
+ channel = guild.create_text_channel("general")
1112
+ started = time.monotonic()
1113
+ result = await alice.slash(channel, "dynamic-offer")
1114
+ assert time.monotonic() - started < 0.25
1115
+ assert result.response is not None
1116
+ await asyncio.sleep(0.4) # past the timeout on the real clock
1117
+ assert timeouts == 0
1118
+ await env.advance_time(0.3)
1119
+ assert timeouts == 1
1120
+ await env.advance_time(0.3)
1121
+ assert timeouts == 1
1122
+
1123
+
1077
1124
  @pytest.mark.asyncio
1078
1125
  async def test_external_wait_covers_timed_awaitable():
1079
1126
  """A declared external_wait around a timed awaitable is fully virtual:
@@ -143,6 +143,65 @@ async def test_view_timeout_task_identity_handles_missing_store() -> None:
143
143
  assert not _dpy_internals._view_timeout_task_identity(client, _fake_task())
144
144
 
145
145
 
146
+ def _impl_coro_task(view: Any, waiter: Any = None) -> tuple[Any, Any]:
147
+ """A fake task running the view's real ``__timeout_task_impl`` coroutine."""
148
+ task = _fake_task(waiter)
149
+ coro = view._BaseView__timeout_task_impl() # never awaited; caller closes it
150
+ task.get_coro = lambda: coro
151
+ return task, coro
152
+
153
+
154
+ async def test_view_timeout_task_identity_matches_impl_coroutine() -> None:
155
+ """A fully-dynamic view lands in no store container; the coroutine itself
156
+ still identifies its expiry task."""
157
+ view = discord.ui.LayoutView(timeout=5)
158
+ task, coro = _impl_coro_task(view)
159
+ try:
160
+ assert _dpy_internals._view_timeout_task_identity(_store_client(), task)
161
+ finally:
162
+ coro.close()
163
+
164
+
165
+ async def test_view_timeout_task_identity_rejects_stopped_impl_owner() -> None:
166
+ view = discord.ui.LayoutView(timeout=5)
167
+ task, coro = _impl_coro_task(view)
168
+ stopped: asyncio.Future[Any] = asyncio.Future()
169
+ stopped.set_result(None)
170
+ stopped_name = next(name for name in vars(view) if name.endswith("__stopped"))
171
+ vars(view)[stopped_name] = stopped
172
+ try:
173
+ assert not _dpy_internals._view_timeout_task_identity(_store_client(), task)
174
+ finally:
175
+ coro.close()
176
+
177
+
178
+ async def test_view_timeout_task_identity_rejects_foreign_coroutine() -> None:
179
+ task = _fake_task()
180
+ task.get_coro = lambda: SimpleNamespace(cr_code=object(), cr_frame=None)
181
+ assert not _dpy_internals._view_timeout_task_identity(_store_client(), task)
182
+
183
+
184
+ async def test_view_timeout_task_identity_ignores_closed_impl_coroutine() -> None:
185
+ """A finished impl coroutine has no live frame, so no owner to match."""
186
+ view = discord.ui.LayoutView(timeout=5)
187
+ coro = view._BaseView__timeout_task_impl()
188
+ coro.close()
189
+ task = _fake_task()
190
+ task.get_coro = lambda: coro
191
+ assert not _dpy_internals._view_timeout_task_identity(_store_client(), task)
192
+
193
+
194
+ async def test_is_intentional_wait_wakeup_impl_coroutine() -> None:
195
+ view = discord.ui.LayoutView(timeout=5)
196
+ task, coro = _impl_coro_task(view)
197
+ try:
198
+ assert _dpy_internals.is_intentional_wait_wakeup(
199
+ _store_client(), _set_result_unless_cancelled, (), task
200
+ )
201
+ finally:
202
+ coro.close()
203
+
204
+
146
205
  async def test_view_expiry_task_requires_pending_waiter() -> None:
147
206
  view = discord.ui.View(timeout=5)
148
207
  task = _task_with_timeout(view)
@@ -454,6 +513,19 @@ async def test_park_reason_names_view_expiry_and_loop_interval(env: Any) -> None
454
513
  assert env._park_reason(loop_task, 5.0) == "discord.ext.tasks loop interval"
455
514
 
456
515
 
516
+ async def test_park_reason_names_unregistered_view_expiry(env: Any) -> None:
517
+ """A fully-dynamic view occupies no store container; its expiry task is
518
+ still a recognized wait, identified by the coroutine it runs."""
519
+ view = discord.ui.LayoutView(timeout=60)
520
+ waiter: asyncio.Future[Any] = asyncio.Future()
521
+ task, coro = _impl_coro_task(view, waiter)
522
+ try:
523
+ _record(env, _set_result_unless_cancelled, (waiter,), 10.0)
524
+ assert env._park_reason(task, 5.0) == "discord View/Modal expiry timer"
525
+ finally:
526
+ coro.close()
527
+
528
+
457
529
  async def test_park_reason_composed_and_sleep_waits(env: Any) -> None:
458
530
  listener: asyncio.Future[Any] = asyncio.Future()
459
531
  env.bot._listeners.setdefault("on_message", []).append((listener, None))
@@ -1780,7 +1780,7 @@ wheels = [
1780
1780
 
1781
1781
  [[package]]
1782
1782
  name = "simcord"
1783
- version = "2.2.0"
1783
+ version = "2.2.1"
1784
1784
  source = { editable = "." }
1785
1785
  dependencies = [
1786
1786
  { name = "discord-py" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes