simple-module-background-tasks 0.0.38__tar.gz → 0.0.40__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 (180) hide show
  1. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/PKG-INFO +7 -7
  2. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/_signal_support.py +2 -1
  3. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/after_commit.py +6 -1
  4. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/api_status.py +2 -1
  5. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/filters.py +4 -3
  6. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/log_purge.py +2 -1
  7. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/log_service.py +8 -5
  8. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_claim.py +6 -5
  9. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_clock.py +3 -5
  10. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_service.py +6 -5
  11. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_worker.py +2 -0
  12. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/Outbox.tsx +21 -9
  13. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/retry_service.py +3 -2
  14. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/task_heartbeat.py +3 -2
  15. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/tasks.py +3 -2
  16. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/package.json +1 -1
  17. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/pyproject.toml +7 -7
  18. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/background_tasks_OutboxPage.test.tsx +13 -5
  19. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/.gitignore +0 -0
  20. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/LICENSE +0 -0
  21. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/README.md +0 -0
  22. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/__init__.py +0 -0
  23. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/_lifecycle.py +0 -0
  24. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/_signal_bus.py +0 -0
  25. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/celery_app.py +0 -0
  26. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/config_guards.py +0 -0
  27. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/constants.py +0 -0
  28. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/contracts/__init__.py +0 -0
  29. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/contracts/events.py +0 -0
  30. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/contracts/logs.py +0 -0
  31. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/contracts/outbox.py +0 -0
  32. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/contracts/schemas.py +0 -0
  33. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/db_settings.py +0 -0
  34. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/deps.py +0 -0
  35. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/__init__.py +0 -0
  36. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/api_admin.py +0 -0
  37. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/logs_api.py +0 -0
  38. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/logs_views.py +0 -0
  39. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/outbox_api.py +0 -0
  40. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/outbox_views.py +0 -0
  41. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/endpoints/views.py +0 -0
  42. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/health.py +0 -0
  43. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/invalidation.py +0 -0
  44. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/locales/de.json +0 -0
  45. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/locales/en.json +0 -0
  46. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/log_context.py +0 -0
  47. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/log_sink.py +0 -0
  48. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/models.py +0 -0
  49. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/module.py +0 -0
  50. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_dispatch.py +0 -0
  51. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_guard.py +0 -0
  52. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_loop.py +0 -0
  53. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_support.py +0 -0
  54. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_sweep.py +0 -0
  55. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/outbox_transport.py +0 -0
  56. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/Detail.tsx +0 -0
  57. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/Index.tsx +0 -0
  58. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/Logs.tsx +0 -0
  59. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/Workers.tsx +0 -0
  60. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/api.generated.ts +0 -0
  61. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/api.ts +0 -0
  62. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/DetailFacts.tsx +0 -0
  63. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/ExecutionRow.tsx +0 -0
  64. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/ExecutionsTable.tsx +0 -0
  65. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/LogLevelSelect.tsx +0 -0
  66. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/LogTable.tsx +0 -0
  67. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/LogsPanel.tsx +0 -0
  68. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/OutboxTable.tsx +0 -0
  69. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/PayloadCards.tsx +0 -0
  70. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/ProgressCard.tsx +0 -0
  71. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/RetryAllDialog.tsx +0 -0
  72. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/RetryConfirmDialog.tsx +0 -0
  73. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/RoutesTable.tsx +0 -0
  74. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/StatusPill.tsx +0 -0
  75. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/StatusStrip.tsx +0 -0
  76. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/TaskFilters.tsx +0 -0
  77. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/TasksEmpty.tsx +0 -0
  78. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/TracebackCard.tsx +0 -0
  79. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/WorkerCard.tsx +0 -0
  80. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/WorkerEmptyStates.tsx +0 -0
  81. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/components/WorkerHealthBanner.tsx +0 -0
  82. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/constants.ts +0 -0
  83. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/logs.ts +0 -0
  84. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/outbox.ts +0 -0
  85. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/retry.ts +0 -0
  86. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/pages/worker-health.ts +0 -0
  87. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/py.typed +0 -0
  88. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/runner.py +0 -0
  89. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/service.py +0 -0
  90. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/services.py +0 -0
  91. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/settings.py +0 -0
  92. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/settings_celery.py +0 -0
  93. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/settings_env_checks.py +0 -0
  94. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/settings_logs.py +0 -0
  95. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/signals.py +0 -0
  96. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/sync_db.py +0 -0
  97. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/task_routing.py +0 -0
  98. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/tenant_context.py +0 -0
  99. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/worker_inspector.py +0 -0
  100. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/worker_settings.py +0 -0
  101. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/background_tasks/workers_state.py +0 -0
  102. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/conftest.py +0 -0
  103. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_admin_api.py +0 -0
  104. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_background_tasks_after_commit.py +0 -0
  105. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_background_tasks_heartbeat.py +0 -0
  106. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_background_tasks_settings.py +0 -0
  107. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_background_tasks_tz_columns.py +0 -0
  108. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_beat_schedule.py +0 -0
  109. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_config_guards.py +0 -0
  110. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_db_timeouts.py +0 -0
  111. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_index_has_executions.py +0 -0
  112. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_index_worker_presence.py +0 -0
  113. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_inprocess_sweep.py +0 -0
  114. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_invalidation.py +0 -0
  115. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_invalidation_degraded.py +0 -0
  116. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_invalidation_redis.py +0 -0
  117. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_list_item_payload.py +0 -0
  118. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_log_api.py +0 -0
  119. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_log_purge.py +0 -0
  120. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_log_sink.py +0 -0
  121. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_log_sink_loop.py +0 -0
  122. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_log_sink_nul.py +0 -0
  123. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_eager_platform.py +0 -0
  124. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_fence_guard.py +0 -0
  125. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_guard_savepoint.py +0 -0
  126. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_loop_interrupt.py +0 -0
  127. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_publish_scope.py +0 -0
  128. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_retry_policy.py +0 -0
  129. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_swallowed_cancel.py +0 -0
  130. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_thread_loops.py +0 -0
  131. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_outbox_worker_chain.py +0 -0
  132. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_page_clamp.py +0 -0
  133. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_pagination_bounds.py +0 -0
  134. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_quiet_sweep_unrecorded.py +0 -0
  135. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_redis_health.py +0 -0
  136. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_redis_url.py +0 -0
  137. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_routes_override_api.py +0 -0
  138. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_runner_settings.py +0 -0
  139. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_service.py +0 -0
  140. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_settings_env.py +0 -0
  141. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_startup_log_masking.py +0 -0
  142. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_task_progress.py +0 -0
  143. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_task_progress_audit.py +0 -0
  144. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_task_routing.py +0 -0
  145. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_task_status_api.py +0 -0
  146. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_users_ordering.py +0 -0
  147. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_worker_audit.py +0 -0
  148. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_worker_audit_bookkeeping.py +0 -0
  149. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_bg_worker_settings.py +0 -0
  150. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_detail_props.py +0 -0
  151. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_execution_tenancy.py +0 -0
  152. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_index_filters.py +0 -0
  153. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_log_context.py +0 -0
  154. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_outbox_admin.py +0 -0
  155. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_outbox_concurrency.py +0 -0
  156. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_outbox_delivery.py +0 -0
  157. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_outbox_tenancy.py +0 -0
  158. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_outbox_worker_task.py +0 -0
  159. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_retry_failed_bulk.py +0 -0
  160. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_retry_failed_guards.py +0 -0
  161. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_retry_sweep_ordering.py +0 -0
  162. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_signal_tenant_stamp.py +0 -0
  163. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_signals.py +0 -0
  164. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_sweep_stuck.py +0 -0
  165. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_sync_db.py +0 -0
  166. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_sync_db_fork.py +0 -0
  167. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_tenant_context.py +0 -0
  168. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_worker_inspector.py +0 -0
  169. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_worker_tenancy.py +0 -0
  170. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_workers_endpoints.py +0 -0
  171. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests/test_workers_uptime.py +0 -0
  172. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/background_tasks_DetailPage.test.tsx +0 -0
  173. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/background_tasks_IndexPage.test.tsx +0 -0
  174. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/background_tasks_LogsTab.test.tsx +0 -0
  175. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/background_tasks_WorkerHealthBanner.test.tsx +0 -0
  176. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/outbox.test.ts +0 -0
  177. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/retry.test.ts +0 -0
  178. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/stub-fetch.ts +0 -0
  179. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tests-js/task-formatters.test.ts +0 -0
  180. {simple_module_background_tasks-0.0.38 → simple_module_background_tasks-0.0.40}/tsconfig.json +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: simple_module_background_tasks
3
- Version: 0.0.38
3
+ Version: 0.0.40
4
4
  Summary: Celery + Redis task queue with admin UI for monitoring and retrying failed/stuck tasks
5
5
  Project-URL: Homepage, https://github.com/antosubash/simple_module_python
6
6
  Project-URL: Repository, https://github.com/antosubash/simple_module_python
@@ -21,12 +21,12 @@ Classifier: Topic :: Internet :: WWW/HTTP
21
21
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
22
  Classifier: Typing :: Typed
23
23
  Requires-Python: >=3.12
24
- Requires-Dist: celery[redis]>=5.4
25
- Requires-Dist: redis>=5
26
- Requires-Dist: simple-module-core==0.0.38
27
- Requires-Dist: simple-module-db==0.0.38
28
- Requires-Dist: simple-module-hosting==0.0.38
29
- Requires-Dist: simple-module-settings==0.0.38
24
+ Requires-Dist: celery[redis]<6,>=5.4
25
+ Requires-Dist: redis<7,>=5
26
+ Requires-Dist: simple-module-core==0.0.40
27
+ Requires-Dist: simple-module-db==0.0.40
28
+ Requires-Dist: simple-module-hosting==0.0.40
29
+ Requires-Dist: simple-module-settings==0.0.40
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  # simple_module_background_tasks
@@ -15,6 +15,7 @@ from typing import Any
15
15
 
16
16
  from sqlalchemy import select
17
17
  from sqlalchemy.orm import Session
18
+ from sqlmodel import col
18
19
 
19
20
  from background_tasks.constants import INTERNAL_TASK_SWEEP_OUTBOX, TaskStatus
20
21
  from background_tasks.models import TaskExecution
@@ -53,7 +54,7 @@ def upsert_by_celery_id(
53
54
  """
54
55
  row: TaskExecution | None = None
55
56
  if celery_task_id:
56
- stmt = select(TaskExecution).where(TaskExecution.celery_task_id == celery_task_id) # ty: ignore[invalid-argument-type]
57
+ stmt = select(TaskExecution).where(col(TaskExecution.celery_task_id) == celery_task_id)
57
58
  row = session.execute(stmt).scalar_one_or_none()
58
59
  if row is None:
59
60
  row = TaskExecution(celery_task_id=celery_task_id, **defaults)
@@ -59,6 +59,11 @@ _HOOKED_KEY = "background_tasks_after_commit_hooked"
59
59
  def enqueue_after_commit(db: AsyncSession | Session, task: Any, *args: Any, **kwargs: Any) -> str:
60
60
  """Send *task* once *db*'s transaction commits; never if it rolls back.
61
61
 
62
+ Delivery is at-most-once: publish intent exists only in memory. Broker
63
+ failure after commit is logged without retry; a process exit between commit
64
+ and publish also loses the job. For must-run work use the event bus's
65
+ ``publish_durable(event, db)`` transactional outbox with a durable handler.
66
+
62
67
  *task* is a Celery task (``send_receipt``), a signature
63
68
  (``send_receipt.s().set(queue="mail")``, for routing options), or a
64
69
  registered task name. ``*args`` / ``**kwargs`` are the task's own
@@ -89,7 +94,7 @@ def after_commit(
89
94
  request. Left off for a single publish so Celery's eager mode — which runs
90
95
  the task body inside the publish — stays on the loop's thread. One failed
91
96
  publish is logged and does not stop the rest: the rows are already
92
- committed, and an unsent ``pending`` row is visible and retryable.
97
+ committed. Only callers with persisted pending work can reconcile failures.
93
98
  """
94
99
  batch = list(publishes)
95
100
  if isinstance(db, RequestSession) and SESSION_START_KEY in db.info:
@@ -18,6 +18,7 @@ from simple_module_db.deps import get_db
18
18
  from simple_module_hosting.permissions import ensure_resolved_permissions
19
19
  from simple_module_hosting.token_guard import Authenticated
20
20
  from sqlalchemy import select
21
+ from sqlmodel import col
21
22
 
22
23
  from background_tasks.constants import MODULE_NAME, PERM_VIEW, STATUS_ROUTER_PREFIX
23
24
  from background_tasks.contracts.schemas import TaskStatusDTO
@@ -56,7 +57,7 @@ async def get_task_status(
56
57
  row = (
57
58
  await db.execute(
58
59
  select(TaskExecution)
59
- .where(TaskExecution.celery_task_id == celery_task_id) # ty: ignore[invalid-argument-type]
60
+ .where(col(TaskExecution.celery_task_id) == celery_task_id)
60
61
  .order_by(TaskExecution.queued_at.desc())
61
62
  .limit(1)
62
63
  )
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
 
11
11
  from simple_module_db import LIKE_ESCAPE_CHAR, like_contains_pattern
12
12
  from sqlalchemy import ColumnElement, select
13
+ from sqlmodel import col
13
14
 
14
15
  from background_tasks.constants import PLATFORM_TENANT_FILTER, RETRYABLE_STATUSES, TaskStatus
15
16
  from background_tasks.models import TaskExecution
@@ -34,15 +35,15 @@ def execution_filters(
34
35
  if tenant_id == PLATFORM_TENANT_FILTER:
35
36
  conditions.append(TaskExecution.tenant_id.is_(None))
36
37
  elif tenant_id:
37
- conditions.append(TaskExecution.tenant_id == tenant_id) # ty: ignore[invalid-argument-type]
38
+ conditions.append(col(TaskExecution.tenant_id) == tenant_id)
38
39
  if status is not None:
39
- conditions.append(TaskExecution.status == status) # ty: ignore[invalid-argument-type]
40
+ conditions.append(col(TaskExecution.status) == status)
40
41
  if task_name:
41
42
  conditions.append(
42
43
  TaskExecution.task_name.ilike(like_contains_pattern(task_name), escape=LIKE_ESCAPE_CHAR)
43
44
  )
44
45
  if queue:
45
- conditions.append(TaskExecution.queue == queue) # ty: ignore[invalid-argument-type]
46
+ conditions.append(col(TaskExecution.queue) == queue)
46
47
  return conditions
47
48
 
48
49
 
@@ -6,11 +6,12 @@ from datetime import datetime
6
6
 
7
7
  from sqlalchemy import delete
8
8
  from sqlalchemy.orm import Session
9
+ from sqlmodel import col
9
10
 
10
11
  from background_tasks.models import RetainedLog
11
12
 
12
13
 
13
14
  def purge_log_records(session: Session, cutoff: datetime) -> int:
14
15
  """Delete log lines older than ``cutoff``; returns the count."""
15
- result = session.execute(delete(RetainedLog).where(RetainedLog.ts < cutoff)) # ty: ignore[invalid-argument-type]
16
+ result = session.execute(delete(RetainedLog).where(col(RetainedLog.ts) < cutoff))
16
17
  return result.rowcount or 0
@@ -9,6 +9,7 @@ from typing import Any
9
9
  from simple_module_db import RequestSession
10
10
  from simple_module_db.pagination import PageParams, paginate
11
11
  from sqlalchemy import false, select
12
+ from sqlmodel import col
12
13
 
13
14
  from background_tasks.constants import LOG_LEVEL_NAMES
14
15
  from background_tasks.contracts.logs import LogItem, LogListResponse
@@ -60,16 +61,18 @@ class LogService:
60
61
  ) -> LogListResponse:
61
62
  where = [*self.scope.conditions()]
62
63
  if task_id is not None:
63
- where.append(RetainedLog.task_id == task_id)
64
+ where.append(col(RetainedLog.task_id) == task_id)
64
65
  if (level := level_number(min_level)) is not None:
65
- where.append(RetainedLog.level >= level)
66
+ where.append(col(RetainedLog.level) >= level)
66
67
  if q:
67
- where.append(RetainedLog.message.ilike(_like(q), escape="\\"))
68
+ where.append(col(RetainedLog.message).ilike(_like(q), escape="\\"))
68
69
  if logger_name:
69
- where.append(RetainedLog.logger.startswith(logger_name))
70
+ where.append(col(RetainedLog.logger).startswith(logger_name))
70
71
  found = await paginate(
71
72
  self.db,
72
- select(RetainedLog).where(*where).order_by(RetainedLog.ts.desc(), RetainedLog.id), # ty: ignore[invalid-argument-type]
73
+ select(RetainedLog)
74
+ .where(*where)
75
+ .order_by(col(RetainedLog.ts).desc(), col(RetainedLog.id)),
73
76
  PageParams(page=page, per_page=per_page),
74
77
  )
75
78
  return LogListResponse(
@@ -20,6 +20,7 @@ from typing import Any
20
20
 
21
21
  from sqlalchemy import or_, select, update
22
22
  from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
23
+ from sqlmodel import col
23
24
 
24
25
  from background_tasks.constants import (
25
26
  OUTBOX_BACKOFF_BASE_SECONDS,
@@ -103,12 +104,12 @@ async def reclaim_expired(
103
104
  async with factory() as session:
104
105
  now = now or await db_now(session)
105
106
  expired = (
106
- OutboxMessage.status == OutboxStatus.RUNNING,
107
- or_(OutboxMessage.locked_until < now, OutboxMessage.locked_until.is_(None)),
107
+ col(OutboxMessage.status) == OutboxStatus.RUNNING,
108
+ or_(col(OutboxMessage.locked_until) < now, col(OutboxMessage.locked_until).is_(None)),
108
109
  )
109
110
  dead = await session.execute(
110
111
  update(OutboxMessage)
111
- .where(*expired, OutboxMessage.attempts >= max_attempts) # ty: ignore[invalid-argument-type]
112
+ .where(*expired, col(OutboxMessage.attempts) >= max_attempts)
112
113
  .values(
113
114
  status=OutboxStatus.DEAD,
114
115
  locked_until=None,
@@ -118,7 +119,7 @@ async def reclaim_expired(
118
119
  )
119
120
  back = await session.execute(
120
121
  update(OutboxMessage)
121
- .where(*expired) # ty: ignore[invalid-argument-type]
122
+ .where(*expired)
122
123
  .values(status=OutboxStatus.PENDING, locked_until=None, next_attempt_at=now)
123
124
  .execution_options(synchronize_session=False)
124
125
  )
@@ -174,7 +175,7 @@ async def claim(
174
175
  update(OutboxMessage)
175
176
  .where(
176
177
  OutboxMessage.id.in_(select(claimable.c.id)),
177
- OutboxMessage.status == OutboxStatus.PENDING, # ty: ignore[invalid-argument-type]
178
+ col(OutboxMessage.status) == OutboxStatus.PENDING,
178
179
  )
179
180
  .values(
180
181
  status=OutboxStatus.RUNNING,
@@ -22,6 +22,8 @@ from __future__ import annotations
22
22
  from datetime import UTC, datetime
23
23
  from typing import Any
24
24
 
25
+ from simple_module_core.datetimes import aware as _aware
26
+ from simple_module_core.datetimes import utcnow
25
27
  from sqlalchemy import func, select
26
28
  from sqlalchemy.orm import Session
27
29
 
@@ -30,10 +32,6 @@ def _is_postgres(session: Any) -> bool:
30
32
  return session.get_bind().dialect.name == "postgresql"
31
33
 
32
34
 
33
- def _aware(value: datetime) -> datetime:
34
- return value if value.tzinfo is not None else value.replace(tzinfo=UTC)
35
-
36
-
37
35
  async def db_now(session: Any) -> datetime:
38
36
  """The database's current time, as an aware UTC ``datetime``.
39
37
 
@@ -41,7 +39,7 @@ async def db_now(session: Any) -> datetime:
41
39
  hold either).
42
40
  """
43
41
  if not _is_postgres(session):
44
- return datetime.now(UTC)
42
+ return utcnow()
45
43
  stmt = select(func.clock_timestamp())
46
44
  if isinstance(session, Session):
47
45
  value = session.execute(stmt).scalar_one()
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any
9
9
  from fastapi import HTTPException, status
10
10
  from simple_module_db.pagination import PageParams, paginate
11
11
  from sqlalchemy import delete, false, func, select, update
12
+ from sqlmodel import col
12
13
 
13
14
  from background_tasks.constants import OutboxStatus
14
15
  from background_tasks.contracts.outbox import OutboxItem, OutboxListResponse
@@ -66,7 +67,7 @@ class OutboxService:
66
67
  self.db,
67
68
  select(OutboxMessage)
68
69
  .where(*where)
69
- .order_by(OutboxMessage.created_at.desc(), OutboxMessage.id), # ty: ignore[invalid-argument-type]
70
+ .order_by(col(OutboxMessage.created_at).desc(), col(OutboxMessage.id)),
70
71
  PageParams(page=page, per_page=per_page),
71
72
  )
72
73
  return OutboxListResponse(
@@ -93,7 +94,7 @@ class OutboxService:
93
94
  row = (
94
95
  await self.db.execute(
95
96
  select(OutboxMessage)
96
- .where(OutboxMessage.id == message_id, *self.scope.conditions()) # ty: ignore[invalid-argument-type]
97
+ .where(col(OutboxMessage.id) == message_id, *self.scope.conditions())
97
98
  .execution_options(populate_existing=True)
98
99
  )
99
100
  ).scalar_one_or_none()
@@ -114,8 +115,8 @@ class OutboxService:
114
115
  result = await self.db.execute(
115
116
  update(OutboxMessage)
116
117
  .where(
117
- OutboxMessage.id == message_id, # ty: ignore[invalid-argument-type]
118
- OutboxMessage.status == OutboxStatus.DEAD, # ty: ignore[invalid-argument-type]
118
+ col(OutboxMessage.id) == message_id,
119
+ col(OutboxMessage.status) == OutboxStatus.DEAD,
119
120
  *self.scope.conditions(),
120
121
  )
121
122
  .values(
@@ -141,7 +142,7 @@ class OutboxService:
141
142
  result = await self.db.execute(
142
143
  delete(OutboxMessage)
143
144
  .where(
144
- OutboxMessage.id == message_id, # ty: ignore[invalid-argument-type]
145
+ col(OutboxMessage.id) == message_id,
145
146
  OutboxMessage.status.in_(DISCARDABLE),
146
147
  *self.scope.conditions(),
147
148
  )
@@ -30,6 +30,7 @@ from simple_module_core.events import EventBus
30
30
  from simple_module_db import init_db
31
31
  from simple_module_db.listeners import attach_session_listeners
32
32
  from simple_module_db.query_filter import bind_engine_policy
33
+ from simple_module_db.tenant_cleanup import register_tenant_cleanup
33
34
  from sqlalchemy.ext.asyncio import AsyncEngine, async_sessionmaker
34
35
 
35
36
  from background_tasks import sync_db
@@ -118,6 +119,7 @@ def build_worker_bus() -> EventBus:
118
119
  from simple_module_core.discovery import discover_modules, topological_sort
119
120
 
120
121
  bus = EventBus()
122
+ register_tenant_cleanup(bus)
121
123
  bus.durable_transport = WorkerOutboxTransport()
122
124
  # Lenient on purpose: the worker delivers whatever is installed, and a
123
125
  # module set the web process refuses is reported there, not here.
@@ -1,11 +1,12 @@
1
1
  import { Head, Link, router } from '@inertiajs/react';
2
2
  import { keys, useT } from '@simple-module-py/i18n';
3
+ import { ConfirmActionDialog } from '@simple-module-py/ui/components/ConfirmActionDialog';
3
4
  import { PageShell } from '@simple-module-py/ui/components/PageShell';
4
5
  import { Button } from '@simple-module-py/ui/components/ui/button';
5
6
  import { usePermissions } from '@simple-module-py/ui/hooks/use-permissions';
6
7
  import { AdminLayout } from '@simple-module-py/ui/layouts/AdminLayout';
7
8
  import { cn } from '@simple-module-py/ui/lib/utils';
8
- import { ArrowLeft } from 'lucide-react';
9
+ import { ArrowLeft, Trash2 } from 'lucide-react';
9
10
  import { useState } from 'react';
10
11
  import { OutboxTable } from './components/OutboxTable';
11
12
  import { VIEW_BASE } from './constants';
@@ -31,6 +32,7 @@ function Outbox({ messages, counts, pagination, filters }: Props) {
31
32
  const { t } = useT();
32
33
  const { can } = usePermissions();
33
34
  const canManage = can('background_tasks.manage');
35
+ const [pendingDiscard, setPendingDiscard] = useState<OutboxMessage | null>(null);
34
36
  const [busyId, setBusyId] = useState<string | null>(null);
35
37
 
36
38
  function visit(status: string, page: number) {
@@ -51,13 +53,6 @@ function Outbox({ messages, counts, pagination, filters }: Props) {
51
53
  }
52
54
  }
53
55
 
54
- function discard(message: OutboxMessage) {
55
- const prompt = t(keys.background_tasks.outbox.discard_confirm, {
56
- handler: message.handler_name,
57
- });
58
- if (window.confirm(prompt)) void act(message, discardOutboxMessage);
59
- }
60
-
61
56
  const segments = [
62
57
  { value: '', label: t(keys.background_tasks.outbox.filter_all), count: null },
63
58
  ...OPEN_OUTBOX_STATUSES.map((s) => ({
@@ -105,13 +100,30 @@ function Outbox({ messages, counts, pagination, filters }: Props) {
105
100
  canManage={canManage}
106
101
  busyId={busyId}
107
102
  onRetry={(m) => void act(m, retryOutboxMessage)}
108
- onDiscard={discard}
103
+ onDiscard={setPendingDiscard}
109
104
  page={pagination.page}
110
105
  perPage={pagination.per_page}
111
106
  total={pagination.total}
112
107
  onPageChange={(page) => visit(filters.status, page)}
113
108
  />
114
109
  </PageShell>
110
+ <ConfirmActionDialog
111
+ open={pendingDiscard !== null}
112
+ onOpenChange={(open) => !open && setPendingDiscard(null)}
113
+ icon={Trash2}
114
+ title={t(keys.background_tasks.outbox.discard)}
115
+ description={t(keys.background_tasks.outbox.discard_confirm, {
116
+ handler: pendingDiscard?.handler_name ?? '',
117
+ })}
118
+ confirmLabel={t(keys.background_tasks.outbox.discard)}
119
+ cancelLabel={t(keys.background_tasks.retry_dialog.cancel)}
120
+ busy={busyId !== null}
121
+ onConfirm={() => {
122
+ if (pendingDiscard) {
123
+ void act(pendingDiscard, discardOutboxMessage).finally(() => setPendingDiscard(null));
124
+ }
125
+ }}
126
+ />
115
127
  </>
116
128
  );
117
129
  }
@@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Any
16
16
  from simple_module_core.events import EventBus
17
17
  from simple_module_db import RequestSession, all_tenants, tenant_context
18
18
  from sqlalchemy import Select, func, select, update
19
+ from sqlmodel import col
19
20
 
20
21
  from background_tasks._signal_support import now_utc
21
22
  from background_tasks.after_commit import after_commit
@@ -271,8 +272,8 @@ def _fail_unsent(task_id: str, exc: Exception) -> None:
271
272
  session.execute(
272
273
  update(TaskExecution)
273
274
  .where(
274
- TaskExecution.celery_task_id == task_id, # ty: ignore[invalid-argument-type]
275
- TaskExecution.status == TaskStatus.PENDING, # ty: ignore[invalid-argument-type]
275
+ col(TaskExecution.celery_task_id) == task_id,
276
+ col(TaskExecution.status) == TaskStatus.PENDING,
276
277
  )
277
278
  .values(
278
279
  status=TaskStatus.FAILED,
@@ -24,6 +24,7 @@ import time
24
24
 
25
25
  from celery import current_task
26
26
  from sqlalchemy import update
27
+ from sqlmodel import col
27
28
 
28
29
  from background_tasks._signal_support import now_utc
29
30
  from background_tasks.constants import (
@@ -122,11 +123,11 @@ def heartbeat(
122
123
  session.execute(
123
124
  update(TaskExecution)
124
125
  .where(
125
- TaskExecution.celery_task_id == task_id, # ty: ignore[invalid-argument-type]
126
+ col(TaskExecution.celery_task_id) == task_id,
126
127
  # Only a row still ``running``: a heartbeat must not revive
127
128
  # a row the sweep already declared stuck (it may have been
128
129
  # retried since) or rewrite a terminal one.
129
- TaskExecution.status == TaskStatus.RUNNING, # ty: ignore[invalid-argument-type]
130
+ col(TaskExecution.status) == TaskStatus.RUNNING,
130
131
  )
131
132
  .values(heartbeat_at=now_utc(), **pending)
132
133
  )
@@ -21,6 +21,7 @@ from typing import Any
21
21
 
22
22
  from celery import Task, shared_task
23
23
  from sqlalchemy import ColumnElement, and_, delete, or_, update
24
+ from sqlmodel import col
24
25
 
25
26
  from background_tasks.constants import (
26
27
  DEMO_ECHO_TASK,
@@ -119,7 +120,7 @@ def sweep_stuck_tasks(self: Task) -> int:
119
120
  stmt = (
120
121
  update(TaskExecution)
121
122
  .where(
122
- TaskExecution.status == TaskStatus.RUNNING, # ty: ignore[invalid-argument-type]
123
+ col(TaskExecution.status) == TaskStatus.RUNNING,
123
124
  _stale(now, stuck_after, overrides),
124
125
  )
125
126
  .values(status=TaskStatus.STUCK, finished_at=now)
@@ -149,7 +150,7 @@ def purge_old_executions() -> int:
149
150
  count = result.rowcount or 0
150
151
  delivered = session.execute(
151
152
  delete(OutboxMessage)
152
- .where(OutboxMessage.status == OutboxStatus.DELIVERED) # ty: ignore[invalid-argument-type]
153
+ .where(col(OutboxMessage.status) == OutboxStatus.DELIVERED)
153
154
  .where(OutboxMessage.delivered_at < cutoff)
154
155
  )
155
156
  count += delivered.rowcount or 0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-module-py/background-tasks",
3
- "version": "0.0.38",
3
+ "version": "0.0.40",
4
4
  "private": true,
5
5
  "description": "Frontend assets for the BackgroundTasks module",
6
6
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simple_module_background_tasks"
3
- version = "0.0.38"
3
+ version = "0.0.40"
4
4
  description = "Celery + Redis task queue with admin UI for monitoring and retrying failed/stuck tasks"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -21,12 +21,12 @@ classifiers = [
21
21
  "Typing :: Typed",
22
22
  ]
23
23
  dependencies = [
24
- "simple_module_core==0.0.38",
25
- "simple_module_db==0.0.38",
26
- "simple_module_hosting==0.0.38",
27
- "simple_module_settings==0.0.38",
28
- "celery[redis]>=5.4",
29
- "redis>=5",
24
+ "simple_module_core==0.0.40",
25
+ "simple_module_db==0.0.40",
26
+ "simple_module_hosting==0.0.40",
27
+ "simple_module_settings==0.0.40",
28
+ "celery[redis]>=5.4,<6",
29
+ "redis>=5,<7",
30
30
  ]
31
31
 
32
32
  [project.entry-points.simple_module]
@@ -1,6 +1,6 @@
1
1
  import '@testing-library/jest-dom/vitest';
2
2
  import { configureI18n, keys } from '@simple-module-py/i18n';
3
- import { fireEvent, render, screen, waitFor } from '@testing-library/react';
3
+ import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
4
4
  import type { ComponentProps } from 'react';
5
5
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
6
6
 
@@ -134,21 +134,27 @@ describe('background_tasks Outbox page', () => {
134
134
 
135
135
  it('asks before discarding and calls nothing when the operator declines', () => {
136
136
  const fetchMock = stubFetch(204);
137
- const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false);
138
137
  render(<Outbox {...(state.props as unknown as ComponentProps<typeof Outbox>)} />);
139
138
  fireEvent.click(screen.getByRole('button', { name: ob.discard }));
140
139
 
141
- expect(confirmSpy).toHaveBeenCalledTimes(1);
140
+ const dialog = screen.getByRole('alertdialog');
141
+ fireEvent.click(
142
+ within(dialog).getByRole('button', { name: keys.background_tasks.retry_dialog.cancel }),
143
+ );
144
+ expect(screen.queryByRole('alertdialog')).toBeNull();
142
145
  expect(fetchMock).not.toHaveBeenCalled();
143
146
  expect(reload).not.toHaveBeenCalled();
144
147
  });
145
148
 
146
149
  it('discards with a DELETE once confirmed', async () => {
147
150
  const fetchMock = stubFetch(204);
148
- vi.spyOn(window, 'confirm').mockReturnValue(true);
149
151
  render(<Outbox {...(state.props as unknown as ComponentProps<typeof Outbox>)} />);
150
152
  fireEvent.click(screen.getByRole('button', { name: ob.discard }));
151
153
 
154
+ expect(fetchMock).not.toHaveBeenCalled();
155
+ fireEvent.click(
156
+ within(screen.getByRole('alertdialog')).getByRole('button', { name: ob.discard }),
157
+ );
152
158
  await waitFor(() => expect(toast.success).toHaveBeenCalledWith(ob.discarded));
153
159
  expect(requestsOf(fetchMock)).toContainEqual({
154
160
  url: '/api/background_tasks/admin/outbox/m1',
@@ -159,9 +165,11 @@ describe('background_tasks Outbox page', () => {
159
165
 
160
166
  it('toasts a discard failure', async () => {
161
167
  stubFetch(500);
162
- vi.spyOn(window, 'confirm').mockReturnValue(true);
163
168
  render(<Outbox {...(state.props as unknown as ComponentProps<typeof Outbox>)} />);
164
169
  fireEvent.click(screen.getByRole('button', { name: ob.discard }));
170
+ fireEvent.click(
171
+ within(screen.getByRole('alertdialog')).getByRole('button', { name: ob.discard }),
172
+ );
165
173
  await waitFor(() => expect(toast.error).toHaveBeenCalledWith(ob.discard_failed));
166
174
  });
167
175