domainkit 0.6.2__tar.gz → 0.8.0__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 (428) hide show
  1. {domainkit-0.6.2 → domainkit-0.8.0}/PKG-INFO +61 -8
  2. {domainkit-0.6.2 → domainkit-0.8.0}/README.md +45 -7
  3. {domainkit-0.6.2 → domainkit-0.8.0}/pyproject.toml +21 -1
  4. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/__init__.py +1 -0
  5. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/apps/config.py +23 -1
  6. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/apps/registry.py +24 -5
  7. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/architecture.py +4 -4
  8. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/base.py +7 -0
  9. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/handler.py +29 -5
  10. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/memory.py +45 -24
  11. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/rabbitmq.py +128 -46
  12. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/__init__.py +6 -0
  13. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/core.py +14 -5
  14. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/memory.py +2 -2
  15. domainkit-0.8.0/src/domainkit/cache/pages.py +236 -0
  16. domainkit-0.8.0/src/domainkit/cache/redis.py +375 -0
  17. domainkit-0.8.0/src/domainkit/clock.py +198 -0
  18. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/__init__.py +2 -0
  19. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/global_settings.py +403 -26
  20. domainkit-0.8.0/src/domainkit/contrib/admin/__init__.py +62 -0
  21. domainkit-0.8.0/src/domainkit/contrib/admin/sites.py +588 -0
  22. domainkit-0.8.0/src/domainkit/contrib/audit/api.py +148 -0
  23. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/apps.py +8 -2
  24. domainkit-0.8.0/src/domainkit/contrib/audit/history.py +206 -0
  25. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/auditforget.py +3 -2
  26. domainkit-0.8.0/src/domainkit/contrib/audit/management/commands/auditlog.py +101 -0
  27. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/auditseal.py +15 -2
  28. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/auditverify.py +2 -0
  29. domainkit-0.8.0/src/domainkit/contrib/audit/models.py +21 -0
  30. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/personal.py +17 -2
  31. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/recorder.py +2 -1
  32. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/seals.py +256 -52
  33. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/settings.py +11 -1
  34. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/tables.py +15 -4
  35. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/__init__.py +8 -0
  36. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/access.py +89 -16
  37. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/admin.py +68 -96
  38. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/api.py +82 -94
  39. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/apps.py +154 -7
  40. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/authentication.py +49 -11
  41. domainkit-0.8.0/src/domainkit/contrib/auth/certificates.py +188 -0
  42. domainkit-0.8.0/src/domainkit/contrib/auth/cookies.py +384 -0
  43. domainkit-0.8.0/src/domainkit/contrib/auth/erasure.py +44 -0
  44. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/events.py +59 -1
  45. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/exceptions.py +4 -0
  46. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/hashers.py +97 -26
  47. domainkit-0.8.0/src/domainkit/contrib/auth/management/commands/addcertificate.py +51 -0
  48. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/createsuperuser.py +7 -2
  49. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/createtoken.py +41 -5
  50. domainkit-0.8.0/src/domainkit/contrib/auth/management/commands/resetmfa.py +42 -0
  51. domainkit-0.8.0/src/domainkit/contrib/auth/mfa.py +479 -0
  52. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/middleware.py +67 -17
  53. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/models.py +163 -15
  54. domainkit-0.8.0/src/domainkit/contrib/auth/onetime.py +119 -0
  55. domainkit-0.8.0/src/domainkit/contrib/auth/personal_tokens.py +220 -0
  56. domainkit-0.8.0/src/domainkit/contrib/auth/recovery.py +352 -0
  57. domainkit-0.8.0/src/domainkit/contrib/auth/repositories.py +745 -0
  58. domainkit-0.8.0/src/domainkit/contrib/auth/settings.py +297 -0
  59. domainkit-0.8.0/src/domainkit/contrib/auth/signup.py +286 -0
  60. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/tables.py +75 -0
  61. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/tasks.py +6 -2
  62. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/email_verification.subject.txt +1 -0
  63. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/email_verification.txt +7 -0
  64. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/password_reset.subject.txt +1 -0
  65. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/password_reset.txt +8 -0
  66. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_confirm.subject.txt +1 -0
  67. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_confirm.txt +8 -0
  68. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_exists.subject.txt +1 -0
  69. domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_exists.txt +7 -0
  70. domainkit-0.8.0/src/domainkit/contrib/auth/throttling.py +124 -0
  71. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/tokens.py +13 -2
  72. domainkit-0.8.0/src/domainkit/contrib/auth/totp.py +84 -0
  73. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/uow.py +12 -1
  74. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/model.py +2 -1
  75. domainkit-0.8.0/src/domainkit/contrib/flags/__init__.py +55 -0
  76. domainkit-0.8.0/src/domainkit/contrib/flags/admin.py +225 -0
  77. domainkit-0.8.0/src/domainkit/contrib/flags/apps.py +32 -0
  78. domainkit-0.8.0/src/domainkit/contrib/flags/dependencies.py +28 -0
  79. domainkit-0.8.0/src/domainkit/contrib/flags/events.py +32 -0
  80. domainkit-0.8.0/src/domainkit/contrib/flags/exceptions.py +15 -0
  81. domainkit-0.8.0/src/domainkit/contrib/flags/management/__init__.py +1 -0
  82. domainkit-0.8.0/src/domainkit/contrib/flags/management/commands/__init__.py +1 -0
  83. domainkit-0.8.0/src/domainkit/contrib/flags/management/commands/flags.py +161 -0
  84. domainkit-0.8.0/src/domainkit/contrib/flags/models.py +113 -0
  85. domainkit-0.8.0/src/domainkit/contrib/flags/settings.py +18 -0
  86. domainkit-0.8.0/src/domainkit/contrib/flags/store.py +208 -0
  87. domainkit-0.8.0/src/domainkit/contrib/flags/tables.py +38 -0
  88. domainkit-0.8.0/src/domainkit/contrib/flags/uow.py +40 -0
  89. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/apps.py +5 -2
  90. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/middleware.py +98 -20
  91. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/operations.py +13 -6
  92. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/settings.py +2 -2
  93. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/tables.py +6 -2
  94. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/tasks.py +3 -4
  95. domainkit-0.8.0/src/domainkit/contrib/mail/__init__.py +56 -0
  96. domainkit-0.8.0/src/domainkit/contrib/mail/apps.py +43 -0
  97. domainkit-0.8.0/src/domainkit/contrib/mail/backends.py +173 -0
  98. domainkit-0.8.0/src/domainkit/contrib/mail/delivery.py +113 -0
  99. domainkit-0.8.0/src/domainkit/contrib/mail/message.py +204 -0
  100. domainkit-0.8.0/src/domainkit/contrib/mail/settings.py +76 -0
  101. domainkit-0.8.0/src/domainkit/contrib/mail/templates.py +157 -0
  102. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/__init__.py +24 -5
  103. domainkit-0.8.0/src/domainkit/contrib/messaging/admin.py +191 -0
  104. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/apps.py +16 -13
  105. domainkit-0.8.0/src/domainkit/contrib/messaging/breaker.py +157 -0
  106. domainkit-0.8.0/src/domainkit/contrib/messaging/budget.py +102 -0
  107. domainkit-0.8.0/src/domainkit/contrib/messaging/cron.py +272 -0
  108. domainkit-0.8.0/src/domainkit/contrib/messaging/dbclock.py +46 -0
  109. domainkit-0.8.0/src/domainkit/contrib/messaging/dispatch.py +559 -0
  110. domainkit-0.8.0/src/domainkit/contrib/messaging/management/commands/messages.py +125 -0
  111. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/management/commands/runworker.py +22 -6
  112. domainkit-0.8.0/src/domainkit/contrib/messaging/metrics.py +151 -0
  113. domainkit-0.8.0/src/domainkit/contrib/messaging/outbox.py +265 -0
  114. domainkit-0.8.0/src/domainkit/contrib/messaging/registry.py +399 -0
  115. domainkit-0.8.0/src/domainkit/contrib/messaging/retry.py +90 -0
  116. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/settings.py +27 -12
  117. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/tables.py +17 -1
  118. domainkit-0.8.0/src/domainkit/contrib/messaging/testing.py +401 -0
  119. domainkit-0.8.0/src/domainkit/contrib/messaging/worker.py +1186 -0
  120. domainkit-0.8.0/src/domainkit/contrib/notifications/__init__.py +65 -0
  121. domainkit-0.8.0/src/domainkit/contrib/notifications/apps.py +60 -0
  122. domainkit-0.8.0/src/domainkit/contrib/notifications/backends.py +107 -0
  123. domainkit-0.8.0/src/domainkit/contrib/notifications/delivery.py +134 -0
  124. domainkit-0.8.0/src/domainkit/contrib/notifications/messages.py +107 -0
  125. domainkit-0.8.0/src/domainkit/contrib/notifications/settings.py +24 -0
  126. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/__init__.py +63 -0
  127. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/apps.py +25 -0
  128. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/backends.py +34 -0
  129. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/criteria.py +178 -0
  130. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/deletions.py +47 -0
  131. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/models.py +146 -0
  132. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/services.py +185 -0
  133. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/tables.py +50 -0
  134. domainkit-0.8.0/src/domainkit/contrib/objectpermissions/uow.py +88 -0
  135. domainkit-0.8.0/src/domainkit/contrib/privacy/__init__.py +55 -0
  136. domainkit-0.8.0/src/domainkit/contrib/privacy/apps.py +38 -0
  137. domainkit-0.8.0/src/domainkit/contrib/privacy/events.py +18 -0
  138. domainkit-0.8.0/src/domainkit/contrib/privacy/management/__init__.py +1 -0
  139. domainkit-0.8.0/src/domainkit/contrib/privacy/management/commands/__init__.py +1 -0
  140. domainkit-0.8.0/src/domainkit/contrib/privacy/management/commands/privacy.py +121 -0
  141. domainkit-0.8.0/src/domainkit/contrib/privacy/models.py +334 -0
  142. domainkit-0.8.0/src/domainkit/contrib/privacy/operations.py +423 -0
  143. domainkit-0.8.0/src/domainkit/contrib/privacy/retention.py +53 -0
  144. domainkit-0.8.0/src/domainkit/contrib/webhooks/__init__.py +35 -0
  145. domainkit-0.8.0/src/domainkit/contrib/webhooks/admin.py +403 -0
  146. domainkit-0.8.0/src/domainkit/contrib/webhooks/apps.py +67 -0
  147. domainkit-0.8.0/src/domainkit/contrib/webhooks/client.py +132 -0
  148. domainkit-0.8.0/src/domainkit/contrib/webhooks/delivery.py +529 -0
  149. domainkit-0.8.0/src/domainkit/contrib/webhooks/guard.py +92 -0
  150. domainkit-0.8.0/src/domainkit/contrib/webhooks/management/__init__.py +1 -0
  151. domainkit-0.8.0/src/domainkit/contrib/webhooks/management/commands/__init__.py +1 -0
  152. domainkit-0.8.0/src/domainkit/contrib/webhooks/management/commands/webhooks.py +133 -0
  153. domainkit-0.8.0/src/domainkit/contrib/webhooks/models.py +240 -0
  154. domainkit-0.8.0/src/domainkit/contrib/webhooks/settings.py +50 -0
  155. domainkit-0.8.0/src/domainkit/contrib/webhooks/signing.py +114 -0
  156. domainkit-0.8.0/src/domainkit/contrib/webhooks/tables.py +89 -0
  157. domainkit-0.8.0/src/domainkit/contrib/webhooks/uow.py +43 -0
  158. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/__init__.py +19 -1
  159. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/connections.py +62 -17
  160. domainkit-0.8.0/src/domainkit/db/derive.py +282 -0
  161. domainkit-0.8.0/src/domainkit/db/drivers.py +282 -0
  162. domainkit-0.8.0/src/domainkit/db/errors.py +302 -0
  163. domainkit-0.8.0/src/domainkit/db/fixtures.py +392 -0
  164. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migrations.py +131 -7
  165. domainkit-0.8.0/src/domainkit/db/orm.py +102 -0
  166. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/partitioning.py +2 -3
  167. domainkit-0.8.0/src/domainkit/db/query.py +232 -0
  168. domainkit-0.8.0/src/domainkit/db/repository.py +182 -0
  169. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/routing.py +31 -1
  170. domainkit-0.8.0/src/domainkit/db/squashing.py +358 -0
  171. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/uow.py +52 -4
  172. domainkit-0.8.0/src/domainkit/ddd/__init__.py +87 -0
  173. domainkit-0.8.0/src/domainkit/ddd/events.py +207 -0
  174. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/exceptions.py +10 -0
  175. domainkit-0.8.0/src/domainkit/ddd/fields.py +341 -0
  176. domainkit-0.8.0/src/domainkit/ddd/messages.py +69 -0
  177. domainkit-0.8.0/src/domainkit/ddd/pagination.py +37 -0
  178. domainkit-0.8.0/src/domainkit/ddd/preconditions.py +111 -0
  179. domainkit-0.8.0/src/domainkit/ddd/query.py +804 -0
  180. domainkit-0.8.0/src/domainkit/ddd/repository.py +610 -0
  181. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/uow.py +101 -4
  182. domainkit-0.8.0/src/domainkit/deadlines.py +317 -0
  183. domainkit-0.8.0/src/domainkit/debug/__init__.py +11 -0
  184. domainkit-0.8.0/src/domainkit/debug/middleware.py +123 -0
  185. domainkit-0.8.0/src/domainkit/debug/queries.py +117 -0
  186. domainkit-0.8.0/src/domainkit/encryption.py +90 -0
  187. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/__init__.py +20 -1
  188. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/application.py +40 -6
  189. domainkit-0.8.0/src/domainkit/http/blocking.py +152 -0
  190. domainkit-0.8.0/src/domainkit/http/client.py +843 -0
  191. domainkit-0.8.0/src/domainkit/http/deadlines.py +141 -0
  192. domainkit-0.8.0/src/domainkit/http/errors.py +464 -0
  193. domainkit-0.8.0/src/domainkit/http/filters.py +430 -0
  194. domainkit-0.8.0/src/domainkit/http/guard.py +411 -0
  195. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/health.py +25 -7
  196. domainkit-0.8.0/src/domainkit/http/live.py +475 -0
  197. domainkit-0.8.0/src/domainkit/http/live_events.py +370 -0
  198. domainkit-0.8.0/src/domainkit/http/locale.py +51 -0
  199. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/metrics.py +36 -0
  200. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/middleware.py +107 -25
  201. domainkit-0.8.0/src/domainkit/http/openapi.py +551 -0
  202. domainkit-0.8.0/src/domainkit/http/pagination.py +561 -0
  203. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/ratelimit.py +219 -39
  204. domainkit-0.8.0/src/domainkit/http/servers.py +229 -0
  205. domainkit-0.8.0/src/domainkit/http/shedding.py +366 -0
  206. domainkit-0.8.0/src/domainkit/http/sse.py +341 -0
  207. domainkit-0.8.0/src/domainkit/http/uploads.py +298 -0
  208. domainkit-0.8.0/src/domainkit/http/versioning.py +204 -0
  209. domainkit-0.8.0/src/domainkit/http/websockets.py +297 -0
  210. domainkit-0.8.0/src/domainkit/i18n/__init__.py +68 -0
  211. domainkit-0.8.0/src/domainkit/i18n/catalogs.py +293 -0
  212. domainkit-0.8.0/src/domainkit/i18n/formats.py +308 -0
  213. domainkit-0.8.0/src/domainkit/i18n/locale/ar/LC_MESSAGES/messages.mo +0 -0
  214. domainkit-0.8.0/src/domainkit/i18n/locale/ar/LC_MESSAGES/messages.po +640 -0
  215. domainkit-0.8.0/src/domainkit/i18n/locale/en/LC_MESSAGES/messages.mo +0 -0
  216. domainkit-0.8.0/src/domainkit/i18n/locale/en/LC_MESSAGES/messages.po +634 -0
  217. domainkit-0.8.0/src/domainkit/i18n/timezone.py +90 -0
  218. domainkit-0.8.0/src/domainkit/i18n/translation.py +271 -0
  219. domainkit-0.8.0/src/domainkit/i18n/validation.py +54 -0
  220. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/__init__.py +2 -0
  221. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/base.py +15 -2
  222. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/handler.py +6 -2
  223. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/immudb.py +169 -70
  224. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/memory.py +13 -2
  225. domainkit-0.8.0/src/domainkit/lifecycle.py +234 -0
  226. domainkit-0.8.0/src/domainkit/log.py +598 -0
  227. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/base.py +11 -4
  228. domainkit-0.8.0/src/domainkit/management/commands/addcommand.py +358 -0
  229. domainkit-0.8.0/src/domainkit/management/commands/addevent.py +197 -0
  230. domainkit-0.8.0/src/domainkit/management/commands/addquery.py +289 -0
  231. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/check.py +19 -2
  232. domainkit-0.8.0/src/domainkit/management/commands/compilemessages.py +101 -0
  233. domainkit-0.8.0/src/domainkit/management/commands/dbshell.py +140 -0
  234. domainkit-0.8.0/src/domainkit/management/commands/diffsettings.py +67 -0
  235. domainkit-0.8.0/src/domainkit/management/commands/dumpdata.py +86 -0
  236. domainkit-0.8.0/src/domainkit/management/commands/flush.py +57 -0
  237. domainkit-0.8.0/src/domainkit/management/commands/loaddata.py +90 -0
  238. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/makemigrations.py +25 -1
  239. domainkit-0.8.0/src/domainkit/management/commands/migrate.py +147 -0
  240. domainkit-0.8.0/src/domainkit/management/commands/openapi.py +111 -0
  241. domainkit-0.8.0/src/domainkit/management/commands/runserver.py +109 -0
  242. domainkit-0.8.0/src/domainkit/management/commands/seed.py +39 -0
  243. domainkit-0.8.0/src/domainkit/management/commands/shell.py +147 -0
  244. domainkit-0.8.0/src/domainkit/management/commands/squashmigrations.py +57 -0
  245. domainkit-0.8.0/src/domainkit/management/commands/startaggregate.py +399 -0
  246. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/startdomain.py +40 -52
  247. domainkit-0.8.0/src/domainkit/management/generators.py +832 -0
  248. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/__init__.py-tpl +2 -2
  249. domainkit-0.8.0/src/domainkit/management/templates/domain/package/api/routes.py-tpl +89 -0
  250. domainkit-0.8.0/src/domainkit/management/templates/domain/package/application/commands.py-tpl +38 -0
  251. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/application/handlers.py-tpl +0 -1
  252. domainkit-0.8.0/src/domainkit/management/templates/domain/package/application/services.py-tpl +62 -0
  253. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/application/unit_of_work.py-tpl +1 -1
  254. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/apps.py-tpl +0 -3
  255. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/domain/events.py-tpl +13 -0
  256. domainkit-0.8.0/src/domainkit/management/templates/domain/package/domain/model.py-tpl +122 -0
  257. domainkit-0.8.0/src/domainkit/management/templates/domain/package/infrastructure/orm.py-tpl +44 -0
  258. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/infrastructure/unit_of_work.py-tpl +11 -3
  259. domainkit-0.8.0/src/domainkit/management/templates/domain/tests/fakes.py-tpl +54 -0
  260. domainkit-0.8.0/src/domainkit/management/templates/domain/tests/test_api.py-tpl +110 -0
  261. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/tests/test_model.py-tpl +52 -11
  262. domainkit-0.8.0/src/domainkit/management/templates/domain/tests/test_services.py-tpl +98 -0
  263. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/pyproject.toml-tpl +7 -1
  264. domainkit-0.8.0/src/domainkit/management/templates/project/src/config/settings/base.py-tpl +44 -0
  265. domainkit-0.8.0/src/domainkit/management/templates/project/src/config/settings/local.py-tpl +16 -0
  266. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/settings/production.py-tpl +16 -6
  267. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/settings/test.py-tpl +1 -0
  268. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/tests/test_health.py-tpl +9 -1
  269. domainkit-0.8.0/src/domainkit/reporting.py +176 -0
  270. domainkit-0.8.0/src/domainkit/resources/__init__.py +59 -0
  271. domainkit-0.8.0/src/domainkit/resources/access.py +102 -0
  272. domainkit-0.8.0/src/domainkit/resources/batches.py +121 -0
  273. domainkit-0.8.0/src/domainkit/resources/conditional.py +64 -0
  274. domainkit-0.8.0/src/domainkit/resources/events.py +34 -0
  275. domainkit-0.8.0/src/domainkit/resources/policies.py +168 -0
  276. domainkit-0.8.0/src/domainkit/resources/renderers.py +174 -0
  277. domainkit-0.8.0/src/domainkit/resources/routing.py +1788 -0
  278. domainkit-0.8.0/src/domainkit/resources/schemas.py +337 -0
  279. domainkit-0.8.0/src/domainkit/signing.py +314 -0
  280. domainkit-0.8.0/src/domainkit/storage/__init__.py +43 -0
  281. domainkit-0.8.0/src/domainkit/storage/base.py +226 -0
  282. domainkit-0.8.0/src/domainkit/storage/filesystem.py +271 -0
  283. domainkit-0.8.0/src/domainkit/storage/handler.py +60 -0
  284. domainkit-0.8.0/src/domainkit/storage/memory.py +63 -0
  285. domainkit-0.8.0/src/domainkit/tasks.py +265 -0
  286. domainkit-0.8.0/src/domainkit/testing/__init__.py +238 -0
  287. domainkit-0.8.0/src/domainkit/testing/blocking.py +128 -0
  288. domainkit-0.8.0/src/domainkit/testing/clients.py +274 -0
  289. domainkit-0.8.0/src/domainkit/testing/databases.py +259 -0
  290. domainkit-0.8.0/src/domainkit/testing/factories.py +313 -0
  291. domainkit-0.8.0/src/domainkit/testing/queries.py +131 -0
  292. domainkit-0.8.0/src/domainkit/tracing.py +150 -0
  293. domainkit-0.8.0/src/domainkit/utils/closing.py +124 -0
  294. domainkit-0.8.0/src/domainkit/utils/headers.py +85 -0
  295. domainkit-0.8.0/src/domainkit/utils/loops.py +57 -0
  296. domainkit-0.8.0/src/domainkit/utils/redis_urls.py +143 -0
  297. domainkit-0.6.2/src/domainkit/cache/redis.py +0 -176
  298. domainkit-0.6.2/src/domainkit/contrib/audit/management/commands/auditlog.py +0 -53
  299. domainkit-0.6.2/src/domainkit/contrib/auth/repositories.py +0 -361
  300. domainkit-0.6.2/src/domainkit/contrib/auth/settings.py +0 -137
  301. domainkit-0.6.2/src/domainkit/contrib/messaging/admin.py +0 -62
  302. domainkit-0.6.2/src/domainkit/contrib/messaging/dispatch.py +0 -316
  303. domainkit-0.6.2/src/domainkit/contrib/messaging/management/commands/messages.py +0 -58
  304. domainkit-0.6.2/src/domainkit/contrib/messaging/metrics.py +0 -57
  305. domainkit-0.6.2/src/domainkit/contrib/messaging/outbox.py +0 -161
  306. domainkit-0.6.2/src/domainkit/contrib/messaging/registry.py +0 -215
  307. domainkit-0.6.2/src/domainkit/contrib/messaging/worker.py +0 -654
  308. domainkit-0.6.2/src/domainkit/db/drivers.py +0 -160
  309. domainkit-0.6.2/src/domainkit/db/errors.py +0 -112
  310. domainkit-0.6.2/src/domainkit/db/orm.py +0 -44
  311. domainkit-0.6.2/src/domainkit/db/repository.py +0 -33
  312. domainkit-0.6.2/src/domainkit/ddd/__init__.py +0 -48
  313. domainkit-0.6.2/src/domainkit/ddd/events.py +0 -127
  314. domainkit-0.6.2/src/domainkit/ddd/messages.py +0 -16
  315. domainkit-0.6.2/src/domainkit/ddd/pagination.py +0 -24
  316. domainkit-0.6.2/src/domainkit/ddd/repository.py +0 -94
  317. domainkit-0.6.2/src/domainkit/http/errors.py +0 -234
  318. domainkit-0.6.2/src/domainkit/http/pagination.py +0 -71
  319. domainkit-0.6.2/src/domainkit/lifecycle.py +0 -38
  320. domainkit-0.6.2/src/domainkit/log.py +0 -119
  321. domainkit-0.6.2/src/domainkit/management/commands/migrate.py +0 -88
  322. domainkit-0.6.2/src/domainkit/management/commands/runserver.py +0 -60
  323. domainkit-0.6.2/src/domainkit/management/commands/shell.py +0 -27
  324. domainkit-0.6.2/src/domainkit/management/templates/domain/package/api/dependencies.py-tpl +0 -47
  325. domainkit-0.6.2/src/domainkit/management/templates/domain/package/api/routes.py-tpl +0 -102
  326. domainkit-0.6.2/src/domainkit/management/templates/domain/package/api/schemas.py-tpl +0 -65
  327. domainkit-0.6.2/src/domainkit/management/templates/domain/package/application/commands.py-tpl +0 -24
  328. domainkit-0.6.2/src/domainkit/management/templates/domain/package/application/services.py-tpl +0 -63
  329. domainkit-0.6.2/src/domainkit/management/templates/domain/package/domain/exceptions.py-tpl +0 -15
  330. domainkit-0.6.2/src/domainkit/management/templates/domain/package/domain/model.py-tpl +0 -71
  331. domainkit-0.6.2/src/domainkit/management/templates/domain/package/domain/repositories.py-tpl +0 -33
  332. domainkit-0.6.2/src/domainkit/management/templates/domain/package/infrastructure/orm.py-tpl +0 -46
  333. domainkit-0.6.2/src/domainkit/management/templates/domain/package/infrastructure/repositories.py-tpl +0 -26
  334. domainkit-0.6.2/src/domainkit/management/templates/domain/package/management/__init__.py-tpl +0 -1
  335. domainkit-0.6.2/src/domainkit/management/templates/domain/package/management/commands/__init__.py-tpl +0 -1
  336. domainkit-0.6.2/src/domainkit/management/templates/domain/package/management/commands/{{ aggregates }}.py-tpl +0 -25
  337. domainkit-0.6.2/src/domainkit/management/templates/domain/package/settings.py-tpl +0 -30
  338. domainkit-0.6.2/src/domainkit/management/templates/domain/tests/fakes.py-tpl +0 -47
  339. domainkit-0.6.2/src/domainkit/management/templates/domain/tests/test_api.py-tpl +0 -121
  340. domainkit-0.6.2/src/domainkit/management/templates/domain/tests/test_services.py-tpl +0 -71
  341. domainkit-0.6.2/src/domainkit/management/templates/domain/tests/test_settings.py-tpl +0 -20
  342. domainkit-0.6.2/src/domainkit/management/templates/project/src/config/settings/base.py-tpl +0 -27
  343. domainkit-0.6.2/src/domainkit/management/templates/project/src/config/settings/local.py-tpl +0 -11
  344. domainkit-0.6.2/src/domainkit/reporting.py +0 -50
  345. domainkit-0.6.2/src/domainkit/testing.py +0 -179
  346. domainkit-0.6.2/src/domainkit/tracing.py +0 -42
  347. domainkit-0.6.2/src/domainkit/utils/closing.py +0 -26
  348. {domainkit-0.6.2 → domainkit-0.8.0}/LICENSE +0 -0
  349. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/apps/__init__.py +0 -0
  350. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/__init__.py +0 -0
  351. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/base.py +0 -0
  352. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/decorators.py +0 -0
  353. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/handler.py +0 -0
  354. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/metrics.py +0 -0
  355. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/env.py +0 -0
  356. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/lazy.py +0 -0
  357. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/context.py +0 -0
  358. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/__init__.py +0 -0
  359. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/__init__.py +0 -0
  360. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/__init__.py +0 -0
  361. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/__init__.py +0 -0
  362. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/protection.py +0 -0
  363. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/tasks.py +0 -0
  364. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/backends.py +0 -0
  365. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/common-passwords.NOTICE +0 -0
  366. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/common-passwords.txt.gz +0 -0
  367. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/__init__.py +0 -0
  368. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/__init__.py +0 -0
  369. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/changepassword.py +0 -0
  370. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/cleartokens.py +0 -0
  371. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/permissions.py +0 -0
  372. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/users.py +0 -0
  373. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/validators.py +0 -0
  374. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/__init__.py +0 -0
  375. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/apps.py +0 -0
  376. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/models.py +0 -0
  377. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/registry.py +0 -0
  378. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/sync.py +0 -0
  379. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/tables.py +0 -0
  380. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/__init__.py +0 -0
  381. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/apps.py +0 -0
  382. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/management/__init__.py +0 -0
  383. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/management/commands/__init__.py +0 -0
  384. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/management/commands/evidence.py +0 -0
  385. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/settings.py +0 -0
  386. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/store.py +0 -0
  387. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/tasks.py +0 -0
  388. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/__init__.py +0 -0
  389. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/management/__init__.py +0 -0
  390. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/management/commands/__init__.py +0 -0
  391. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migration_env/__init__.py +0 -0
  392. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migration_env/env.py +0 -0
  393. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migration_env/script.py.mako +0 -0
  394. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/snapshots.py +0 -0
  395. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/types.py +0 -0
  396. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/_dataclass.py +0 -0
  397. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/model.py +0 -0
  398. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/personal.py +0 -0
  399. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/deprecation.py +0 -0
  400. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/exceptions.py +0 -0
  401. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/__init__.py +0 -0
  402. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/__init__.py +0 -0
  403. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/broker.py +0 -0
  404. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/cache.py +0 -0
  405. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/init.py +0 -0
  406. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/partitions.py +0 -0
  407. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/showmigrations.py +0 -0
  408. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/api/__init__.py-tpl +0 -0
  409. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/application/__init__.py-tpl +0 -0
  410. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/domain/__init__.py-tpl +0 -0
  411. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/infrastructure/__init__.py-tpl +0 -0
  412. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/tests/__init__.py-tpl +0 -0
  413. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/dot-env-tpl +0 -0
  414. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/dot-gitignore-tpl +0 -0
  415. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/manage.py-tpl +0 -0
  416. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/__init__.py-tpl +0 -0
  417. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/asgi.py-tpl +0 -0
  418. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/settings/__init__.py-tpl +0 -0
  419. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/urls.py-tpl +0 -0
  420. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/domains/__init__.py-tpl +0 -0
  421. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/tests/__init__.py-tpl +0 -0
  422. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/py.typed +0 -0
  423. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/__init__.py +0 -0
  424. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/formatting.py +0 -0
  425. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/module_loading.py +0 -0
  426. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/rates.py +0 -0
  427. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/readiness.py +0 -0
  428. {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/text.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: domainkit
3
- Version: 0.6.2
3
+ Version: 0.8.0
4
4
  Summary: A Django-style framework for domain-driven FastAPI services.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -17,22 +17,34 @@ Classifier: Programming Language :: Python :: 3.14
17
17
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
18
18
  Classifier: Typing :: Typed
19
19
  Provides-Extra: argon2
20
+ Provides-Extra: granian
21
+ Provides-Extra: httpclient
20
22
  Provides-Extra: immudb
21
23
  Provides-Extra: jwt
22
24
  Provides-Extra: metrics
25
+ Provides-Extra: mfa
23
26
  Provides-Extra: postgres
24
27
  Provides-Extra: rabbitmq
25
28
  Provides-Extra: redis
26
29
  Provides-Extra: sqlite
27
30
  Provides-Extra: testing
28
31
  Provides-Extra: tracing
32
+ Provides-Extra: tzdata
33
+ Provides-Extra: uploads
34
+ Provides-Extra: uvloop
35
+ Provides-Extra: webhooks
29
36
  Requires-Dist: aio-pika (>=10.1.0,<11.0.0) ; extra == "rabbitmq"
30
37
  Requires-Dist: aiosqlite (>=0.22.1,<0.23.0) ; extra == "sqlite"
31
38
  Requires-Dist: alembic (>=1.20.0,<2.0.0)
32
39
  Requires-Dist: argon2-cffi (>=25.1.0,<26.0.0) ; extra == "argon2"
33
40
  Requires-Dist: asyncpg (>=0.31.0,<0.32.0) ; extra == "postgres"
41
+ Requires-Dist: cryptography (>=50.0.2,<51.0.0) ; extra == "mfa"
42
+ Requires-Dist: cryptography (>=50.0.2,<51.0.0) ; extra == "webhooks"
34
43
  Requires-Dist: fastapi (>=0.141.1,<0.142.0)
44
+ Requires-Dist: granian (==2.8.4) ; extra == "granian"
45
+ Requires-Dist: httpx2 (>=2.13.0,<3.0.0) ; extra == "httpclient"
35
46
  Requires-Dist: httpx2 (>=2.13.0,<3.0.0) ; extra == "testing"
47
+ Requires-Dist: httpx2 (>=2.13.0,<3.0.0) ; extra == "webhooks"
36
48
  Requires-Dist: immudb-py (>=1.5.0,<1.6.0) ; extra == "immudb"
37
49
  Requires-Dist: opentelemetry-api (>=1.45.1,<2.0.0) ; extra == "tracing"
38
50
  Requires-Dist: prometheus-client (>=0.26.0,<0.27.0) ; extra == "metrics"
@@ -40,15 +52,22 @@ Requires-Dist: pydantic (>=2.14.1,<3.0.0)
40
52
  Requires-Dist: pyjwt[crypto] (>=2.10.1,<3.0.0) ; extra == "jwt"
41
53
  Requires-Dist: pytest (>=9.1.1,<10.0.0) ; extra == "testing"
42
54
  Requires-Dist: python-dotenv (>=1.2.4,<2.0.0)
55
+ Requires-Dist: python-multipart (>=0.0.32,<0.1.0) ; extra == "uploads"
43
56
  Requires-Dist: redis (>=8.1.0,<9.0.0) ; extra == "redis"
44
57
  Requires-Dist: sqlalchemy[asyncio] (>=2.0.54,<2.1)
58
+ Requires-Dist: tzdata (>=2025.2) ; extra == "tzdata"
45
59
  Requires-Dist: uvicorn[standard] (>=0.54.0,<0.55.0)
60
+ Requires-Dist: uvloop (==0.23.0) ; (sys_platform != "win32") and (extra == "uvloop")
46
61
  Project-URL: Changelog, https://github.com/karimmattar/domainkit/blob/main/CHANGELOG.md
47
62
  Project-URL: Documentation, https://github.com/karimmattar/domainkit/blob/main/docs/reference.md
48
63
  Project-URL: Homepage, https://github.com/karimmattar/domainkit
49
64
  Project-URL: Issues, https://github.com/karimmattar/domainkit/issues
50
65
  Description-Content-Type: text/markdown
51
66
 
67
+ <p align="center">
68
+ <img src="https://raw.githubusercontent.com/karimmattar/domainkit/main/docs/assets/domainkit-banner.jpg" alt="domainkit, a domain-driven FastAPI framework" width="720">
69
+ </p>
70
+
52
71
  # domainkit
53
72
 
54
73
  A Django-style framework for domain-driven FastAPI services. It provides:
@@ -58,8 +77,13 @@ A Django-style framework for domain-driven FastAPI services. It provides:
58
77
  - DDD building blocks, with the layer rules between them enforced
59
78
  - several databases: domains routed to their own, sharding by key,
60
79
  PostgreSQL range partitioning, and immudb ledgers for evidence
61
- - caches in memory or in Redis and Valkey, with stampede protection, and
62
- rate limits shared by every replica
80
+ - caches in memory or in Redis and Valkey (one server, Sentinel, or
81
+ Cluster), with stampede protection, and rate limits shared by every
82
+ replica, globally or per route
83
+ - answers in the caller's language (English and Arabic ship), times shown
84
+ in the business's time zone, and numbers and dates in local formats
85
+ - file storage and uploads checked by their content, under names the
86
+ storage makes
63
87
  - background work through a transactional outbox, run by polling workers or
64
88
  through RabbitMQ, with queues for separate worker pools
65
89
  - users, groups, and permissions per content type, as in Django's
@@ -67,24 +91,53 @@ A Django-style framework for domain-driven FastAPI services. It provides:
67
91
  and a swappable user model; token or JWT authentication, and route rules
68
92
  as in Django REST framework
69
93
  - optional apps for reliable messaging, audit trails, idempotent requests,
70
- and tamper-evident evidence
94
+ tamper-evident evidence, signed outgoing webhooks, feature flags, and privacy
95
+ requests (export, erasure, and retention)
71
96
 
72
97
  ```bash
73
- pip install "domainkit[postgres]" # extras: postgres, sqlite, argon2, jwt, immudb, redis, rabbitmq, metrics, tracing, testing
98
+ pip install "domainkit[postgres]" # extras: postgres, sqlite, argon2, jwt, mfa, immudb, redis, rabbitmq, metrics, tracing, uploads, testing, webhooks, tzdata
74
99
  ```
75
100
 
76
101
  ## Documentation
77
102
 
78
103
  - [docs/reference.md](https://github.com/karimmattar/domainkit/blob/main/docs/reference.md): settings, middleware, errors,
79
- observability, testing, commands, and extension points
104
+ the API surface (OpenAPI, versions, deprecations), observability,
105
+ testing, commands, and extension points
80
106
  - [docs/databases.md](https://github.com/karimmattar/domainkit/blob/main/docs/databases.md): several databases, routing,
81
107
  sharding, partitioning, and ledgers
108
+ - [docs/queries.md](https://github.com/karimmattar/domainkit/blob/main/docs/queries.md): querying repositories,
109
+ pages, filters, soft delete, row locks, and savepoints
110
+ - [docs/resources.md](https://github.com/karimmattar/domainkit/blob/main/docs/resources.md): aggregates over REST:
111
+ resource routes, commands and queries, derived schemas, partial changes,
112
+ ETags and conditional writes, field policies, nesting, the generators,
113
+ and the generic admin
114
+ - [docs/runtime.md](https://github.com/karimmattar/domainkit/blob/main/docs/runtime.md): how async work runs:
115
+ background tasks, clean contexts, the shutdown budget, readiness,
116
+ event-handler timeouts, and workers and the outbox
82
117
  - [docs/cache.md](https://github.com/karimmattar/domainkit/blob/main/docs/cache.md): caches in memory, Redis, and Valkey,
83
118
  and shared rate limits
119
+ - [docs/runtime.md](https://github.com/karimmattar/domainkit/blob/main/docs/runtime.md): running under load: request
120
+ deadlines, load shedding, database timeouts and PgBouncer, and outgoing
121
+ HTTP calls
84
122
  - [docs/contrib.md](https://github.com/karimmattar/domainkit/blob/main/docs/contrib.md): messaging (and workers on RabbitMQ),
85
- audit, idempotency, and evidence
123
+ webhooks, audit and records' history, idempotency, evidence, and
124
+ feature flags
86
125
  - [docs/auth.md](https://github.com/karimmattar/domainkit/blob/main/docs/auth.md): users, groups, permissions, content
87
126
  types, passwords, backends, tokens, route rules, and the admin API
127
+ - [docs/mail.md](https://github.com/karimmattar/domainkit/blob/main/docs/mail.md): email, mail templates, text messages,
128
+ and push notifications, sent through the outbox
129
+ - [docs/i18n.md](https://github.com/karimmattar/domainkit/blob/main/docs/i18n.md): responses in the caller's language,
130
+ time zones, and number and date formats
131
+ - [docs/uploads.md](https://github.com/karimmattar/domainkit/blob/main/docs/uploads.md): file storages, receiving uploads,
132
+ and serving files
133
+ - [docs/live.md](https://github.com/karimmattar/domainkit/blob/main/docs/live.md): WebSockets and Server-Sent Events,
134
+ bounded and re-authorized while open, and committed events streamed live
135
+ - [docs/privacy.md](https://github.com/karimmattar/domainkit/blob/main/docs/privacy.md): exporting and erasing a person's
136
+ data, and retention
137
+ - [docs/testing.md](https://github.com/karimmattar/domainkit/blob/main/docs/testing.md): the pytest plugin: test databases,
138
+ sync and async clients, factories, a clock to freeze, and query counts
139
+ - [docs/runtime.md](https://github.com/karimmattar/domainkit/blob/main/docs/runtime.md): how async work runs: event loops,
140
+ logging off the loop, finding blocking code, free-threaded Python, and servers
88
141
  - [docs/versioning.md](https://github.com/karimmattar/domainkit/blob/main/docs/versioning.md): compatibility, deprecations, and releases
89
142
  - [docs/adr/](https://github.com/karimmattar/domainkit/tree/main/docs/adr): the decisions behind the design
90
143
  - [CHANGELOG.md](https://github.com/karimmattar/domainkit/blob/main/CHANGELOG.md) and [SECURITY.md](https://github.com/karimmattar/domainkit/blob/main/SECURITY.md)
@@ -97,7 +150,7 @@ pip install "domainkit[postgres,sqlite,testing]"
97
150
  domainkit init billingAPI billing # the name sets the variable prefix: BILLING_API_*
98
151
  cd billing
99
152
  python manage.py check
100
- python manage.py startdomain invoices --aggregate Invoice # prints how to install it
153
+ python manage.py startdomain invoices --aggregate Invoice # installs and mounts it
101
154
  python manage.py makemigrations -m "create invoices" && python manage.py migrate
102
155
  python manage.py createsuperuser
103
156
  pytest
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/karimmattar/domainkit/main/docs/assets/domainkit-banner.jpg" alt="domainkit, a domain-driven FastAPI framework" width="720">
3
+ </p>
4
+
1
5
  # domainkit
2
6
 
3
7
  A Django-style framework for domain-driven FastAPI services. It provides:
@@ -7,8 +11,13 @@ A Django-style framework for domain-driven FastAPI services. It provides:
7
11
  - DDD building blocks, with the layer rules between them enforced
8
12
  - several databases: domains routed to their own, sharding by key,
9
13
  PostgreSQL range partitioning, and immudb ledgers for evidence
10
- - caches in memory or in Redis and Valkey, with stampede protection, and
11
- rate limits shared by every replica
14
+ - caches in memory or in Redis and Valkey (one server, Sentinel, or
15
+ Cluster), with stampede protection, and rate limits shared by every
16
+ replica, globally or per route
17
+ - answers in the caller's language (English and Arabic ship), times shown
18
+ in the business's time zone, and numbers and dates in local formats
19
+ - file storage and uploads checked by their content, under names the
20
+ storage makes
12
21
  - background work through a transactional outbox, run by polling workers or
13
22
  through RabbitMQ, with queues for separate worker pools
14
23
  - users, groups, and permissions per content type, as in Django's
@@ -16,24 +25,53 @@ A Django-style framework for domain-driven FastAPI services. It provides:
16
25
  and a swappable user model; token or JWT authentication, and route rules
17
26
  as in Django REST framework
18
27
  - optional apps for reliable messaging, audit trails, idempotent requests,
19
- and tamper-evident evidence
28
+ tamper-evident evidence, signed outgoing webhooks, feature flags, and privacy
29
+ requests (export, erasure, and retention)
20
30
 
21
31
  ```bash
22
- pip install "domainkit[postgres]" # extras: postgres, sqlite, argon2, jwt, immudb, redis, rabbitmq, metrics, tracing, testing
32
+ pip install "domainkit[postgres]" # extras: postgres, sqlite, argon2, jwt, mfa, immudb, redis, rabbitmq, metrics, tracing, uploads, testing, webhooks, tzdata
23
33
  ```
24
34
 
25
35
  ## Documentation
26
36
 
27
37
  - [docs/reference.md](https://github.com/karimmattar/domainkit/blob/main/docs/reference.md): settings, middleware, errors,
28
- observability, testing, commands, and extension points
38
+ the API surface (OpenAPI, versions, deprecations), observability,
39
+ testing, commands, and extension points
29
40
  - [docs/databases.md](https://github.com/karimmattar/domainkit/blob/main/docs/databases.md): several databases, routing,
30
41
  sharding, partitioning, and ledgers
42
+ - [docs/queries.md](https://github.com/karimmattar/domainkit/blob/main/docs/queries.md): querying repositories,
43
+ pages, filters, soft delete, row locks, and savepoints
44
+ - [docs/resources.md](https://github.com/karimmattar/domainkit/blob/main/docs/resources.md): aggregates over REST:
45
+ resource routes, commands and queries, derived schemas, partial changes,
46
+ ETags and conditional writes, field policies, nesting, the generators,
47
+ and the generic admin
48
+ - [docs/runtime.md](https://github.com/karimmattar/domainkit/blob/main/docs/runtime.md): how async work runs:
49
+ background tasks, clean contexts, the shutdown budget, readiness,
50
+ event-handler timeouts, and workers and the outbox
31
51
  - [docs/cache.md](https://github.com/karimmattar/domainkit/blob/main/docs/cache.md): caches in memory, Redis, and Valkey,
32
52
  and shared rate limits
53
+ - [docs/runtime.md](https://github.com/karimmattar/domainkit/blob/main/docs/runtime.md): running under load: request
54
+ deadlines, load shedding, database timeouts and PgBouncer, and outgoing
55
+ HTTP calls
33
56
  - [docs/contrib.md](https://github.com/karimmattar/domainkit/blob/main/docs/contrib.md): messaging (and workers on RabbitMQ),
34
- audit, idempotency, and evidence
57
+ webhooks, audit and records' history, idempotency, evidence, and
58
+ feature flags
35
59
  - [docs/auth.md](https://github.com/karimmattar/domainkit/blob/main/docs/auth.md): users, groups, permissions, content
36
60
  types, passwords, backends, tokens, route rules, and the admin API
61
+ - [docs/mail.md](https://github.com/karimmattar/domainkit/blob/main/docs/mail.md): email, mail templates, text messages,
62
+ and push notifications, sent through the outbox
63
+ - [docs/i18n.md](https://github.com/karimmattar/domainkit/blob/main/docs/i18n.md): responses in the caller's language,
64
+ time zones, and number and date formats
65
+ - [docs/uploads.md](https://github.com/karimmattar/domainkit/blob/main/docs/uploads.md): file storages, receiving uploads,
66
+ and serving files
67
+ - [docs/live.md](https://github.com/karimmattar/domainkit/blob/main/docs/live.md): WebSockets and Server-Sent Events,
68
+ bounded and re-authorized while open, and committed events streamed live
69
+ - [docs/privacy.md](https://github.com/karimmattar/domainkit/blob/main/docs/privacy.md): exporting and erasing a person's
70
+ data, and retention
71
+ - [docs/testing.md](https://github.com/karimmattar/domainkit/blob/main/docs/testing.md): the pytest plugin: test databases,
72
+ sync and async clients, factories, a clock to freeze, and query counts
73
+ - [docs/runtime.md](https://github.com/karimmattar/domainkit/blob/main/docs/runtime.md): how async work runs: event loops,
74
+ logging off the loop, finding blocking code, free-threaded Python, and servers
37
75
  - [docs/versioning.md](https://github.com/karimmattar/domainkit/blob/main/docs/versioning.md): compatibility, deprecations, and releases
38
76
  - [docs/adr/](https://github.com/karimmattar/domainkit/tree/main/docs/adr): the decisions behind the design
39
77
  - [CHANGELOG.md](https://github.com/karimmattar/domainkit/blob/main/CHANGELOG.md) and [SECURITY.md](https://github.com/karimmattar/domainkit/blob/main/SECURITY.md)
@@ -46,7 +84,7 @@ pip install "domainkit[postgres,sqlite,testing]"
46
84
  domainkit init billingAPI billing # the name sets the variable prefix: BILLING_API_*
47
85
  cd billing
48
86
  python manage.py check
49
- python manage.py startdomain invoices --aggregate Invoice # prints how to install it
87
+ python manage.py startdomain invoices --aggregate Invoice # installs and mounts it
50
88
  python manage.py makemigrations -m "create invoices" && python manage.py migrate
51
89
  python manage.py createsuperuser
52
90
  pytest
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "domainkit"
3
- version = "0.6.2"
3
+ version = "0.8.0"
4
4
  description = "A Django-style framework for domain-driven FastAPI services."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -40,14 +40,31 @@ argon2 = ["argon2-cffi (>=25.1.0,<26.0.0)"]
40
40
  # JSON Web Tokens for domainkit.contrib.auth (JWTAuthentication), with the
41
41
  # RSA, ECDSA, and EdDSA algorithms.
42
42
  jwt = ["pyjwt[crypto] (>=2.10.1,<3.0.0)"]
43
+ # Second factors for domainkit.contrib.auth: AES-GCM for the TOTP secrets
44
+ # it stores (auth.mfa).
45
+ mfa = ["cryptography (>=50.0.2,<51.0.0)"]
43
46
  # Ledgers for evidence (WORM): immudb's SDK, with its verified operations.
44
47
  immudb = ["immudb-py (>=1.5.0,<1.6.0)"]
45
48
  # A message broker for the worker: RabbitMQ, through aio-pika.
46
49
  rabbitmq = ["aio-pika (>=10.1.0,<11.0.0)"]
47
50
  # Shared caches and rate limits: Redis or Valkey, through redis-py.
48
51
  redis = ["redis (>=8.1.0,<9.0.0)"]
52
+ # File uploads (domainkit.http.uploads): FastAPI reads multipart forms with it.
53
+ uploads = ["python-multipart (>=0.0.32,<0.1.0)"]
49
54
  # The domainkit.testing pytest plugin; Starlette's TestClient needs httpx2.
50
55
  testing = ["pytest (>=9.1.1,<10.0.0)", "httpx2 (>=2.13.0,<3.0.0)"]
56
+ # Outgoing webhooks (domainkit.contrib.webhooks) send with httpx2 and encrypt
57
+ # subscription secrets with AES-GCM.
58
+ webhooks = ["httpx2 (>=2.13.0,<3.0.0)", "cryptography (>=50.0.2,<51.0.0)"]
59
+ # Outgoing HTTP calls (domainkit.http.client): pooled, bounded by the
60
+ # request's deadline, retried, with a circuit breaker per host.
61
+ httpclient = ["httpx2 (>=2.13.0,<3.0.0)"]
62
+ # The IANA time zones for cron schedules where the system has none (Windows).
63
+ tzdata = ["tzdata (>=2025.2)"]
64
+ # uvloop's event loop for management commands and runserver (EVENT_LOOP).
65
+ uvloop = ["uvloop (==0.23.0) ; sys_platform != 'win32'"]
66
+ # Granian, an ASGI server in Rust: "runserver --server granian", or production.
67
+ granian = ["granian (==2.8.4)"]
51
68
 
52
69
  # Runs commands before a project exists: "domainkit init billingAPI".
53
70
  [project.scripts]
@@ -151,3 +168,6 @@ exclude_also = ["if TYPE_CHECKING:", "raise NotImplementedError"]
151
168
  source_paths = ["src"]
152
169
  also_copy = ["domainkit_tests/", "docs/"]
153
170
  pytest_add_cli_args_test_selection = ["domainkit_tests/", "-p", "no:randomly"]
171
+ # Fork from a clean server: children forked after the suite ran hang on
172
+ # database tests, which mutmut would count as killed by timeout.
173
+ process_isolation = "forkserver"
@@ -37,4 +37,5 @@ def setup() -> None:
37
37
  event_bus.error_hook = lambda exc, event: report_error(
38
38
  exc, event=type(event).__qualname__, event_id=str(event.event_id)
39
39
  )
40
+ event_bus.handler_timeout = settings.EVENT_HANDLER_TIMEOUT
40
41
  registry.populate(settings.INSTALLED_DOMAINS)
@@ -4,7 +4,7 @@ from collections.abc import Sequence
4
4
  from importlib import import_module
5
5
  from pathlib import Path
6
6
  from types import ModuleType
7
- from typing import TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, ClassVar
8
8
 
9
9
  from pydantic import BaseModel
10
10
 
@@ -15,6 +15,7 @@ if TYPE_CHECKING:
15
15
  from sqlalchemy import Table
16
16
  from sqlalchemy.ext.asyncio import AsyncConnection
17
17
 
18
+ from domainkit.contrib.privacy import Retention
18
19
  from domainkit.db.routing import DatabaseRouter
19
20
  from domainkit.ddd import DeferredTask, DomainEvent, UnitOfWork
20
21
 
@@ -34,6 +35,8 @@ class DomainConfig:
34
35
  verbose_name: str = ""
35
36
  settings_class: type[BaseModel] | None = None
36
37
  """Schema for this domain's settings, read from ``settings_name``."""
38
+ RETENTION: ClassVar[Sequence["Retention"]] = ()
39
+ """How long rows of its tables are kept (``domainkit.contrib.privacy``)."""
37
40
 
38
41
  def __init__(self, name: str, module: ModuleType) -> None:
39
42
  """Bind the config to the domain package ``name``."""
@@ -120,6 +123,16 @@ class DomainConfig:
120
123
  Content types and permissions are created this way.
121
124
  """
122
125
 
126
+ async def seed(self) -> None:
127
+ """Add the rows a fresh database needs to be useful (``manage.py seed``).
128
+
129
+ Sample data for development, or defaults a deployment starts with.
130
+ ``seed`` calls the hooks in ``INSTALLED_DOMAINS`` order, so a domain
131
+ may use what those before it seeded. Write rows through the domain's
132
+ own units of work, and make running twice harmless: rows that exist
133
+ stay as they are.
134
+ """
135
+
123
136
  def check_application(self, app: "FastAPI") -> list[str]:
124
137
  """Problems with the built application, such as routes no rule guards.
125
138
 
@@ -128,6 +141,15 @@ class DomainConfig:
128
141
  """
129
142
  return []
130
143
 
144
+ def deployment_warnings(self) -> list[str]:
145
+ """Weak but valid choices of this domain's settings where deployed.
146
+
147
+ ``check`` lists them after ``Settings.deployment_warnings``; they
148
+ do not stop the application. Return none outside staging and
149
+ production.
150
+ """
151
+ return []
152
+
131
153
  def holds_table(self, table: "Table") -> bool:
132
154
  """Whether ``table``, tagged with this domain, is in use now.
133
155
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  import difflib
4
4
  from collections.abc import AsyncIterator, Iterable, Iterator
5
- from contextlib import AsyncExitStack, asynccontextmanager
5
+ from contextlib import asynccontextmanager
6
6
  from typing import Final, cast
7
7
 
8
8
  from pydantic import BaseModel, ValidationError
@@ -10,6 +10,7 @@ from pydantic import BaseModel, ValidationError
10
10
  from domainkit.apps.config import DomainConfig
11
11
  from domainkit.conf import describe_errors, settings, unwrap_settings
12
12
  from domainkit.exceptions import ImproperlyConfigured
13
+ from domainkit.utils.closing import close_in_turn
13
14
  from domainkit.utils.readiness import failing_checks
14
15
 
15
16
 
@@ -94,13 +95,26 @@ class DomainRegistry:
94
95
  return value
95
96
 
96
97
  @asynccontextmanager
97
- async def lifespan(self) -> AsyncIterator[None]:
98
- """Start domains in order and stop started ones in reverse, even on failure."""
99
- async with AsyncExitStack() as stack:
98
+ async def lifespan(
99
+ self, *, stop_within: float | None = None
100
+ ) -> AsyncIterator[None]:
101
+ """Start domains in order and stop started ones in reverse, even on failure.
102
+
103
+ Stopping takes at most ``stop_within`` seconds (None: no limit). A
104
+ domain may use what is left of them, less a little kept for each
105
+ domain still to stop (``close_in_turn``): one whose ``shutdown``
106
+ hangs is cut off, and the domains started before it still stop.
107
+ Every domain is stopped; the first failure is raised, and later ones
108
+ are logged.
109
+ """
110
+ started: list[DomainConfig] = []
111
+ try:
100
112
  for config in self:
101
113
  await config.startup()
102
- stack.push_async_callback(config.shutdown)
114
+ started.append(config)
103
115
  yield
116
+ finally:
117
+ await close_in_turn(started[::-1], _stop, within=stop_within)
104
118
 
105
119
  async def check(self, seconds: float) -> list[str]:
106
120
  """Run every domain's readiness check; return the labels that failed."""
@@ -132,6 +146,11 @@ class DomainRegistry:
132
146
  )
133
147
 
134
148
 
149
+ async def _stop(config: DomainConfig) -> None:
150
+ """Run the domain's own ``shutdown``."""
151
+ await config.shutdown()
152
+
153
+
135
154
  def _describe_unknown(name: str, known: list[str]) -> str:
136
155
  """Name an unknown setting and suggest the closest known one."""
137
156
  matches = difflib.get_close_matches(name, known, n=1)
@@ -4,8 +4,8 @@ Each domain package has four layers; dependencies point inward only::
4
4
 
5
5
  api -> infrastructure -> application -> domain
6
6
 
7
- - ``domain``: the standard library, ``domainkit.ddd``, and its own domain layer.
8
- No frameworks, no I/O, no settings.
7
+ - ``domain``: the standard library, ``domainkit.ddd``, ``domainkit.clock``, and
8
+ its own domain layer. No frameworks, no I/O, no settings.
9
9
  - ``application``: no ``infrastructure`` or ``api`` code, and no web or
10
10
  database libraries; it reaches storage through repository ports.
11
11
  - ``infrastructure``: no ``api`` code and no web libraries.
@@ -26,7 +26,7 @@ from typing import Final
26
26
 
27
27
  LAYERS: Final = ("domain", "application", "infrastructure", "api")
28
28
 
29
- DOMAIN_ALLOWED_PREFIXES: Final = ("domainkit.ddd",)
29
+ DOMAIN_ALLOWED_PREFIXES: Final = ("domainkit.ddd", "domainkit.clock")
30
30
  """Non-stdlib modules a domain layer may import (besides its own layer)."""
31
31
 
32
32
  FORBIDDEN_LIBRARIES: Final = {
@@ -127,7 +127,7 @@ def _layer_rule(package: str, layer: str | None, imported: str) -> str | None:
127
127
  if not _is_stdlib(imported) and not _within(imported, allowed):
128
128
  return (
129
129
  "the domain layer may import only the standard library, "
130
- "domainkit.ddd, and its own domain layer"
130
+ "domainkit.ddd, domainkit.clock, and its own domain layer"
131
131
  )
132
132
  return None
133
133
  if layer is None:
@@ -51,6 +51,13 @@ class Subscription(ABC):
51
51
  class Broker(ABC):
52
52
  """Durable work queues. Queue names are the application's; adapters prefix them."""
53
53
 
54
+ publish_timeout: float = 10.0
55
+ """Most seconds ``publish`` may take, connecting first included.
56
+
57
+ Relays lock the messages they publish for a little longer than this, so
58
+ a publication cut short (a crash, a stop) is retried soon after.
59
+ """
60
+
54
61
  @abstractmethod
55
62
  async def declare(self, queues: Iterable[str]) -> None:
56
63
  """Create the queues if they do not exist yet."""
@@ -5,6 +5,7 @@ loop that opened them, so each running loop gets its own brokers.
5
5
  """
6
6
 
7
7
  import asyncio
8
+ import threading
8
9
  from collections.abc import Callable
9
10
  from typing import Final
10
11
  from weakref import WeakKeyDictionary
@@ -42,6 +43,8 @@ class BrokerHandler:
42
43
  self._replaced: WeakKeyDictionary[asyncio.AbstractEventLoop, list[Broker]] = (
43
44
  WeakKeyDictionary()
44
45
  )
46
+ self._lock = threading.Lock()
47
+ """Held to change or walk the registries: loops on other threads do too."""
45
48
 
46
49
  def config(self, alias: str) -> BrokerConfig:
47
50
  """Return the ``BROKERS`` entry of ``alias``."""
@@ -60,21 +63,42 @@ class BrokerHandler:
60
63
  f"brokers[{alias!r}] needs a running event loop: connections "
61
64
  "belong to one, so look brokers up inside async code."
62
65
  ) from None
63
- brokers = self._brokers.setdefault(loop, {})
66
+ with self._lock:
67
+ if loop not in self._brokers:
68
+ self._forget_closed_loops()
69
+ brokers = self._brokers.setdefault(loop, {})
64
70
  current = brokers.get(alias)
65
71
  if current is not None and current[0] == config:
66
72
  return current[1]
67
73
  if current is not None: # override_settings changed it; close it later.
68
- self._replaced.setdefault(loop, []).append(current[1])
74
+ with self._lock:
75
+ replaced = self._replaced.setdefault(loop, [])
76
+ replaced.append(current[1])
69
77
  broker = MemoryBroker() if config.scheme == "memory" else self._connect(config)
70
78
  brokers[alias] = (config, broker)
71
79
  return broker
72
80
 
81
+ def _forget_closed_loops(self) -> None:
82
+ """Drop the brokers of loops closed without ``close_all``.
83
+
84
+ Their connections died with the loop, and a broker that references
85
+ its loop would keep it alive for good. Called with ``_lock`` held.
86
+ """
87
+ for loop in [loop for loop in self._brokers if loop.is_closed()]:
88
+ self._brokers.pop(loop, None)
89
+ self._replaced.pop(loop, None)
90
+
73
91
  async def close_all(self) -> None:
74
- """Close the running loop's brokers; the next use opens new ones."""
92
+ """Close the running loop's brokers; the next use opens new ones.
93
+
94
+ Each broker's own ``close``: ``Broker.close`` is the base class's,
95
+ which closes nothing.
96
+ """
75
97
  loop = asyncio.get_running_loop()
76
- current = [broker for _, broker in self._brokers.pop(loop, {}).values()]
77
- await close_each([*current, *self._replaced.pop(loop, [])], Broker.close)
98
+ with self._lock:
99
+ current = [broker for _, broker in self._brokers.pop(loop, {}).values()]
100
+ replaced = self._replaced.pop(loop, [])
101
+ await close_each([*current, *replaced], lambda broker: broker.close())
78
102
 
79
103
 
80
104
  brokers: Final = BrokerHandler()
@@ -6,7 +6,8 @@ staging and production refuse it.
6
6
  """
7
7
 
8
8
  import asyncio
9
- from collections.abc import Iterable
9
+ from collections import deque
10
+ from collections.abc import Iterable, Sequence
10
11
 
11
12
  from domainkit.broker.base import Broker, BrokerRefused, Delivery, Handler, Subscription
12
13
 
@@ -14,17 +15,16 @@ type _Message = tuple[bytes, str]
14
15
 
15
16
 
16
17
  class _MemorySubscription(Subscription):
17
- """The pumps that hand queued messages to a handler."""
18
+ """The pump that hands queued messages to a handler."""
18
19
 
19
- def __init__(self, pumps: list[asyncio.Task[None]]) -> None:
20
- """Own ``pumps``."""
21
- self._pumps = pumps
20
+ def __init__(self, pump: asyncio.Task[None]) -> None:
21
+ """Own ``pump``."""
22
+ self._pump = pump
22
23
 
23
24
  async def cancel(self) -> None:
24
- """Stop the pumps; handlers already running finish."""
25
- for pump in self._pumps:
26
- pump.cancel()
27
- await asyncio.gather(*self._pumps, return_exceptions=True)
25
+ """Stop the pump; handlers already running finish."""
26
+ self._pump.cancel()
27
+ await asyncio.gather(self._pump, return_exceptions=True)
28
28
 
29
29
 
30
30
  class MemoryBroker(Broker):
@@ -32,54 +32,75 @@ class MemoryBroker(Broker):
32
32
 
33
33
  def __init__(self) -> None:
34
34
  """Start without queues."""
35
- self._queues: dict[str, asyncio.Queue[_Message]] = {}
35
+ self._queues: dict[str, deque[_Message]] = {}
36
+ self._arrived = asyncio.Event()
36
37
  self._handling: set[asyncio.Task[None]] = set()
37
38
 
38
39
  async def declare(self, queues: Iterable[str]) -> None:
39
40
  """Create the missing queues."""
40
41
  for queue in queues:
41
- self._queues.setdefault(queue, asyncio.Queue())
42
+ self._queues.setdefault(queue, deque())
42
43
 
43
44
  async def publish(self, queue: str, body: bytes, *, message_id: str) -> None:
44
45
  """Append to ``queue``; refuse queues nobody declared, as RabbitMQ does."""
45
46
  if queue not in self._queues:
46
47
  raise BrokerRefused(f"No queue {queue!r} takes the message.")
47
- self._queues[queue].put_nowait((body, message_id))
48
+ self._put(queue, (body, message_id))
48
49
 
49
50
  def pending(self, queue: str) -> int:
50
51
  """Messages waiting in ``queue`` (tests); 0 for a queue never declared."""
51
- return self._queues[queue].qsize() if queue in self._queues else 0
52
+ return len(self._queues.get(queue, ()))
52
53
 
53
54
  async def consume(
54
55
  self, queues: Iterable[str], prefetch: int, handler: Handler
55
56
  ) -> Subscription:
56
- """Pump each queue into ``handler``, at most ``prefetch`` at once."""
57
+ """Pump the queues into ``handler``, at most ``prefetch`` at once in all."""
57
58
  queues = list(queues)
58
59
  await self.declare(queues)
59
- slots = asyncio.Semaphore(prefetch)
60
- pumps = [
61
- asyncio.create_task(self._pump(queue, slots, handler)) for queue in queues
62
- ]
63
- return _MemorySubscription(pumps)
60
+ pump = asyncio.create_task(
61
+ self._pump(queues, asyncio.Semaphore(prefetch), handler)
62
+ )
63
+ return _MemorySubscription(pump)
64
+
65
+ def _put(self, queue: str, message: _Message) -> None:
66
+ """Queue ``message`` and wake the pumps."""
67
+ self._queues[queue].append(message)
68
+ self._arrived.set()
64
69
 
65
70
  async def _pump(
66
- self, queue: str, slots: asyncio.Semaphore, handler: Handler
71
+ self, queues: Sequence[str], slots: asyncio.Semaphore, handler: Handler
67
72
  ) -> None:
68
- """Hand out ``queue``'s messages while a slot is free."""
69
- source = self._queues[queue]
73
+ """Hand out the queues' messages, in turn, while a slot is free.
74
+
75
+ One pump serves every queue of a subscription, so while it waits for
76
+ a message it holds one slot, not one per queue: an empty queue never
77
+ keeps a slot from a busy one.
78
+ """
79
+ turn = 0
70
80
  while True:
71
81
  await slots.acquire()
72
82
  try:
73
- body, message_id = await source.get()
83
+ queue, (body, message_id) = await self._next(queues, turn)
74
84
  except asyncio.CancelledError:
75
85
  slots.release()
76
86
  raise
87
+ turn = queues.index(queue) + 1
77
88
  task = asyncio.create_task(
78
89
  self._deliver(queue, body, message_id, slots, handler)
79
90
  )
80
91
  self._handling.add(task)
81
92
  task.add_done_callback(self._handling.discard)
82
93
 
94
+ async def _next(self, queues: Sequence[str], turn: int) -> tuple[str, _Message]:
95
+ """Take the next message, looking at ``queues`` from ``turn`` on."""
96
+ while True:
97
+ for offset in range(len(queues)):
98
+ queue = queues[(turn + offset) % len(queues)]
99
+ if self._queues[queue]:
100
+ return queue, self._queues[queue].popleft()
101
+ self._arrived.clear()
102
+ await self._arrived.wait()
103
+
83
104
  async def _deliver(
84
105
  self,
85
106
  queue: str,
@@ -108,6 +129,6 @@ class MemoryBroker(Broker):
108
129
 
109
130
  async def requeue() -> None:
110
131
  if settle():
111
- self._queues[queue].put_nowait((body, message_id))
132
+ self._put(queue, (body, message_id))
112
133
 
113
134
  await handler(Delivery(queue, body, message_id, ack, requeue))