domainkit 0.3.0__tar.gz → 0.4.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 (166) hide show
  1. {domainkit-0.3.0 → domainkit-0.4.0}/PKG-INFO +25 -9
  2. {domainkit-0.3.0 → domainkit-0.4.0}/README.md +22 -8
  3. {domainkit-0.3.0 → domainkit-0.4.0}/pyproject.toml +7 -1
  4. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/architecture.py +1 -0
  5. domainkit-0.4.0/src/domainkit/cache/__init__.py +39 -0
  6. domainkit-0.4.0/src/domainkit/cache/base.py +63 -0
  7. domainkit-0.4.0/src/domainkit/cache/core.py +400 -0
  8. domainkit-0.4.0/src/domainkit/cache/decorators.py +126 -0
  9. domainkit-0.4.0/src/domainkit/cache/handler.py +122 -0
  10. domainkit-0.4.0/src/domainkit/cache/memory.py +155 -0
  11. domainkit-0.4.0/src/domainkit/cache/metrics.py +24 -0
  12. domainkit-0.4.0/src/domainkit/cache/redis.py +176 -0
  13. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/__init__.py +4 -0
  14. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/global_settings.py +123 -1
  15. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/application.py +3 -1
  16. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/health.py +2 -0
  17. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/ratelimit.py +60 -2
  18. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/base.py +2 -0
  19. domainkit-0.4.0/src/domainkit/management/commands/cache.py +48 -0
  20. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/testing.py +10 -0
  21. {domainkit-0.3.0 → domainkit-0.4.0}/LICENSE +0 -0
  22. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/__init__.py +0 -0
  23. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/apps/__init__.py +0 -0
  24. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/apps/config.py +0 -0
  25. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/apps/registry.py +0 -0
  26. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/env.py +0 -0
  27. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/lazy.py +0 -0
  28. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/context.py +0 -0
  29. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/__init__.py +0 -0
  30. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/__init__.py +0 -0
  31. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/apps.py +0 -0
  32. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/management/__init__.py +0 -0
  33. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/management/commands/__init__.py +0 -0
  34. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/management/commands/auditlog.py +0 -0
  35. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/protection.py +0 -0
  36. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/recorder.py +0 -0
  37. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/settings.py +0 -0
  38. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/tables.py +0 -0
  39. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/__init__.py +0 -0
  40. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/apps.py +0 -0
  41. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/management/__init__.py +0 -0
  42. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/management/commands/__init__.py +0 -0
  43. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/management/commands/evidence.py +0 -0
  44. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/model.py +0 -0
  45. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/settings.py +0 -0
  46. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/store.py +0 -0
  47. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/tasks.py +0 -0
  48. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/__init__.py +0 -0
  49. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/apps.py +0 -0
  50. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/middleware.py +0 -0
  51. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/settings.py +0 -0
  52. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/tables.py +0 -0
  53. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/tasks.py +0 -0
  54. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/__init__.py +0 -0
  55. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/admin.py +0 -0
  56. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/apps.py +0 -0
  57. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/__init__.py +0 -0
  58. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/commands/__init__.py +0 -0
  59. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/commands/messages.py +0 -0
  60. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/commands/runworker.py +0 -0
  61. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/metrics.py +0 -0
  62. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/outbox.py +0 -0
  63. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/registry.py +0 -0
  64. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/settings.py +0 -0
  65. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/tables.py +0 -0
  66. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/worker.py +0 -0
  67. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/__init__.py +0 -0
  68. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/connections.py +0 -0
  69. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/drivers.py +0 -0
  70. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migration_env/__init__.py +0 -0
  71. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migration_env/env.py +0 -0
  72. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migration_env/script.py.mako +0 -0
  73. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migrations.py +0 -0
  74. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/orm.py +0 -0
  75. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/partitioning.py +0 -0
  76. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/repository.py +0 -0
  77. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/routing.py +0 -0
  78. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/types.py +0 -0
  79. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/uow.py +0 -0
  80. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/__init__.py +0 -0
  81. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/_dataclass.py +0 -0
  82. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/events.py +0 -0
  83. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/exceptions.py +0 -0
  84. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/messages.py +0 -0
  85. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/model.py +0 -0
  86. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/pagination.py +0 -0
  87. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/repository.py +0 -0
  88. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/uow.py +0 -0
  89. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/deprecation.py +0 -0
  90. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/exceptions.py +0 -0
  91. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/__init__.py +0 -0
  92. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/errors.py +0 -0
  93. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/metrics.py +0 -0
  94. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/middleware.py +0 -0
  95. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/pagination.py +0 -0
  96. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/__init__.py +0 -0
  97. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/base.py +0 -0
  98. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/handler.py +0 -0
  99. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/immudb.py +0 -0
  100. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/memory.py +0 -0
  101. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/log.py +0 -0
  102. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/__init__.py +0 -0
  103. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/__init__.py +0 -0
  104. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/check.py +0 -0
  105. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/init.py +0 -0
  106. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/makemigrations.py +0 -0
  107. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/migrate.py +0 -0
  108. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/partitions.py +0 -0
  109. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/runserver.py +0 -0
  110. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/shell.py +0 -0
  111. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/showmigrations.py +0 -0
  112. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/startdomain.py +0 -0
  113. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/__init__.py-tpl +0 -0
  114. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/__init__.py-tpl +0 -0
  115. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/dependencies.py-tpl +0 -0
  116. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/routes.py-tpl +0 -0
  117. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/schemas.py-tpl +0 -0
  118. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/__init__.py-tpl +0 -0
  119. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/commands.py-tpl +0 -0
  120. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/handlers.py-tpl +0 -0
  121. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/services.py-tpl +0 -0
  122. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/unit_of_work.py-tpl +0 -0
  123. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/apps.py-tpl +0 -0
  124. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/__init__.py-tpl +0 -0
  125. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/events.py-tpl +0 -0
  126. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/exceptions.py-tpl +0 -0
  127. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/model.py-tpl +0 -0
  128. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/repositories.py-tpl +0 -0
  129. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/__init__.py-tpl +0 -0
  130. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/orm.py-tpl +0 -0
  131. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/repositories.py-tpl +0 -0
  132. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/unit_of_work.py-tpl +0 -0
  133. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/management/__init__.py-tpl +0 -0
  134. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/management/commands/__init__.py-tpl +0 -0
  135. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/management/commands/{{ aggregates }}.py-tpl +0 -0
  136. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/settings.py-tpl +0 -0
  137. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/__init__.py-tpl +0 -0
  138. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/fakes.py-tpl +0 -0
  139. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_api.py-tpl +0 -0
  140. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_model.py-tpl +0 -0
  141. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_services.py-tpl +0 -0
  142. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_settings.py-tpl +0 -0
  143. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/dot-env-tpl +0 -0
  144. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/dot-gitignore-tpl +0 -0
  145. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/manage.py-tpl +0 -0
  146. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/pyproject.toml-tpl +0 -0
  147. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/__init__.py-tpl +0 -0
  148. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/asgi.py-tpl +0 -0
  149. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/__init__.py-tpl +0 -0
  150. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/base.py-tpl +0 -0
  151. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/local.py-tpl +0 -0
  152. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/production.py-tpl +0 -0
  153. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/test.py-tpl +0 -0
  154. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/urls.py-tpl +0 -0
  155. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/domains/__init__.py-tpl +0 -0
  156. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/tests/__init__.py-tpl +0 -0
  157. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/tests/test_health.py-tpl +0 -0
  158. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/py.typed +0 -0
  159. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/reporting.py +0 -0
  160. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/tracing.py +0 -0
  161. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/__init__.py +0 -0
  162. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/formatting.py +0 -0
  163. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/module_loading.py +0 -0
  164. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/rates.py +0 -0
  165. {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/readiness.py +0 -0
  166. {domainkit-0.3.0 → domainkit-0.4.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.3.0
3
+ Version: 0.4.0
4
4
  Summary: A Django-style framework for domain-driven FastAPI services.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -19,6 +19,7 @@ Classifier: Typing :: Typed
19
19
  Provides-Extra: immudb
20
20
  Provides-Extra: metrics
21
21
  Provides-Extra: postgres
22
+ Provides-Extra: redis
22
23
  Provides-Extra: sqlite
23
24
  Provides-Extra: testing
24
25
  Provides-Extra: tracing
@@ -33,6 +34,7 @@ Requires-Dist: prometheus-client (>=0.26.0,<0.27.0) ; extra == "metrics"
33
34
  Requires-Dist: pydantic (>=2.14.1,<3.0.0)
34
35
  Requires-Dist: pytest (>=9.1.1,<10.0.0) ; extra == "testing"
35
36
  Requires-Dist: python-dotenv (>=1.2.4,<2.0.0)
37
+ Requires-Dist: redis (>=8.1.0,<9.0.0) ; extra == "redis"
36
38
  Requires-Dist: sqlalchemy[asyncio] (>=2.0.54,<2.1)
37
39
  Requires-Dist: uvicorn[standard] (>=0.54.0,<0.55.0)
38
40
  Project-URL: Changelog, https://github.com/karimmattar/domainkit/blob/main/CHANGELOG.md
@@ -50,11 +52,13 @@ A Django-style framework for domain-driven FastAPI services. It provides:
50
52
  - DDD building blocks, with the layer rules between them enforced
51
53
  - several databases: domains routed to their own, sharding by key,
52
54
  PostgreSQL range partitioning, and immudb ledgers for evidence
55
+ - caches in memory or in Redis and Valkey, with stampede protection, and
56
+ rate limits shared by every replica
53
57
  - optional apps for reliable messaging, audit trails, idempotent requests,
54
58
  and tamper-evident evidence
55
59
 
56
60
  ```bash
57
- pip install "domainkit[postgres]" # extras: postgres, sqlite, immudb, metrics, tracing, testing
61
+ pip install "domainkit[postgres]" # extras: postgres, sqlite, immudb, redis, metrics, tracing, testing
58
62
  ```
59
63
 
60
64
  ## Documentation
@@ -63,6 +67,8 @@ pip install "domainkit[postgres]" # extras: postgres, sqlite, immudb, metric
63
67
  observability, testing, commands, and extension points
64
68
  - [docs/databases.md](https://github.com/karimmattar/domainkit/blob/main/docs/databases.md): several databases, routing,
65
69
  sharding, partitioning, and ledgers
70
+ - [docs/cache.md](https://github.com/karimmattar/domainkit/blob/main/docs/cache.md): caches in memory, Redis, and Valkey,
71
+ and shared rate limits
66
72
  - [docs/contrib.md](https://github.com/karimmattar/domainkit/blob/main/docs/contrib.md): messaging, audit, idempotency, and evidence
67
73
  - [docs/versioning.md](https://github.com/karimmattar/domainkit/blob/main/docs/versioning.md): compatibility, deprecations, and releases
68
74
  - [docs/adr/](https://github.com/karimmattar/domainkit/tree/main/docs/adr): the decisions behind the design
@@ -112,24 +118,34 @@ poetry run pytest --cov # random order; coverage gate in pyprojec
112
118
  poetry run mutmut run "domainkit.http*" && poetry run mutmut results
113
119
  ```
114
120
 
115
- The database tests use SQLite unless `DOMAINKIT_TEST_POSTGRES_URL` names a
116
- PostgreSQL server; each test then creates a database there and drops it.
117
- Use a throwaway server:
121
+ Without servers, the database tests use SQLite, the Redis tests use an
122
+ in-memory fake (fakeredis), and the tests that need PostgreSQL, immudb, or
123
+ a real Redis or Valkey are skipped. Point them at throwaway servers to run
124
+ them; each test cleans up after itself:
118
125
 
119
126
  ```bash
120
127
  docker run -d --rm --name domainkit-postgres -e POSTGRES_PASSWORD=postgres \
121
128
  -p 127.0.0.1:55432:5432 postgres:18-alpine
129
+ docker run -d --rm --name domainkit-redis -p 127.0.0.1:56379:6379 redis:8-alpine
122
130
  DOMAINKIT_TEST_POSTGRES_URL=postgresql+asyncpg://postgres:postgres@127.0.0.1:55432/postgres \
131
+ DOMAINKIT_TEST_REDIS_URL=redis://127.0.0.1:56379/0 \
123
132
  poetry run pytest
124
133
  ```
125
134
 
126
- CI (`.github/workflows/ci.yml`) runs the same checks on every push and pull
127
- request:
135
+ The other variables (immudb, Redis over TLS, Valkey) are listed at the top
136
+ of `domainkit_tests/test_immudb.py` and `test_redis.py`. Each run must cover
137
+ 98%; CI also combines the runs with and without servers, which must cover
138
+ 100%. To check that locally, run `pytest --cov` once without and once with
139
+ the servers, each with its own `COVERAGE_FILE`, then `coverage combine` and
140
+ `coverage report --fail-under=100`.
141
+
142
+ CI (`.github/workflows/ci.yml`) runs on every push and pull request:
128
143
 
129
144
  - lint and strict typing
130
145
  - the tests on Python 3.13 and 3.14
131
- - the tests again with PostgreSQL 18, including row locking, the audit
132
- triggers, and server-side timeouts
146
+ - the tests again against PostgreSQL 18, immudb with TLS and a signing key,
147
+ Redis 8 (plain and TLS), and Valkey 9; no test may be skipped there
148
+ - coverage of both runs combined, which must be 100%
133
149
  - a wheel build, installed and imported in a clean environment
134
150
  - pip-audit, and a gitleaks scan of the whole history
135
151
 
@@ -7,11 +7,13 @@ A Django-style framework for domain-driven FastAPI services. It provides:
7
7
  - DDD building blocks, with the layer rules between them enforced
8
8
  - several databases: domains routed to their own, sharding by key,
9
9
  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
10
12
  - optional apps for reliable messaging, audit trails, idempotent requests,
11
13
  and tamper-evident evidence
12
14
 
13
15
  ```bash
14
- pip install "domainkit[postgres]" # extras: postgres, sqlite, immudb, metrics, tracing, testing
16
+ pip install "domainkit[postgres]" # extras: postgres, sqlite, immudb, redis, metrics, tracing, testing
15
17
  ```
16
18
 
17
19
  ## Documentation
@@ -20,6 +22,8 @@ pip install "domainkit[postgres]" # extras: postgres, sqlite, immudb, metric
20
22
  observability, testing, commands, and extension points
21
23
  - [docs/databases.md](https://github.com/karimmattar/domainkit/blob/main/docs/databases.md): several databases, routing,
22
24
  sharding, partitioning, and ledgers
25
+ - [docs/cache.md](https://github.com/karimmattar/domainkit/blob/main/docs/cache.md): caches in memory, Redis, and Valkey,
26
+ and shared rate limits
23
27
  - [docs/contrib.md](https://github.com/karimmattar/domainkit/blob/main/docs/contrib.md): messaging, audit, idempotency, and evidence
24
28
  - [docs/versioning.md](https://github.com/karimmattar/domainkit/blob/main/docs/versioning.md): compatibility, deprecations, and releases
25
29
  - [docs/adr/](https://github.com/karimmattar/domainkit/tree/main/docs/adr): the decisions behind the design
@@ -69,24 +73,34 @@ poetry run pytest --cov # random order; coverage gate in pyprojec
69
73
  poetry run mutmut run "domainkit.http*" && poetry run mutmut results
70
74
  ```
71
75
 
72
- The database tests use SQLite unless `DOMAINKIT_TEST_POSTGRES_URL` names a
73
- PostgreSQL server; each test then creates a database there and drops it.
74
- Use a throwaway server:
76
+ Without servers, the database tests use SQLite, the Redis tests use an
77
+ in-memory fake (fakeredis), and the tests that need PostgreSQL, immudb, or
78
+ a real Redis or Valkey are skipped. Point them at throwaway servers to run
79
+ them; each test cleans up after itself:
75
80
 
76
81
  ```bash
77
82
  docker run -d --rm --name domainkit-postgres -e POSTGRES_PASSWORD=postgres \
78
83
  -p 127.0.0.1:55432:5432 postgres:18-alpine
84
+ docker run -d --rm --name domainkit-redis -p 127.0.0.1:56379:6379 redis:8-alpine
79
85
  DOMAINKIT_TEST_POSTGRES_URL=postgresql+asyncpg://postgres:postgres@127.0.0.1:55432/postgres \
86
+ DOMAINKIT_TEST_REDIS_URL=redis://127.0.0.1:56379/0 \
80
87
  poetry run pytest
81
88
  ```
82
89
 
83
- CI (`.github/workflows/ci.yml`) runs the same checks on every push and pull
84
- request:
90
+ The other variables (immudb, Redis over TLS, Valkey) are listed at the top
91
+ of `domainkit_tests/test_immudb.py` and `test_redis.py`. Each run must cover
92
+ 98%; CI also combines the runs with and without servers, which must cover
93
+ 100%. To check that locally, run `pytest --cov` once without and once with
94
+ the servers, each with its own `COVERAGE_FILE`, then `coverage combine` and
95
+ `coverage report --fail-under=100`.
96
+
97
+ CI (`.github/workflows/ci.yml`) runs on every push and pull request:
85
98
 
86
99
  - lint and strict typing
87
100
  - the tests on Python 3.13 and 3.14
88
- - the tests again with PostgreSQL 18, including row locking, the audit
89
- triggers, and server-side timeouts
101
+ - the tests again against PostgreSQL 18, immudb with TLS and a signing key,
102
+ Redis 8 (plain and TLS), and Valkey 9; no test may be skipped there
103
+ - coverage of both runs combined, which must be 100%
90
104
  - a wheel build, installed and imported in a clean environment
91
105
  - pip-audit, and a gitleaks scan of the whole history
92
106
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "domainkit"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "A Django-style framework for domain-driven FastAPI services."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -37,6 +37,8 @@ metrics = ["prometheus-client (>=0.26.0,<0.27.0)"]
37
37
  tracing = ["opentelemetry-api (>=1.45.1,<2.0.0)"]
38
38
  # Ledgers for evidence (WORM): immudb's SDK, with its verified operations.
39
39
  immudb = ["immudb-py (>=1.5.0,<1.6.0)"]
40
+ # Shared caches and rate limits: Redis or Valkey, through redis-py.
41
+ redis = ["redis (>=8.1.0,<9.0.0)"]
40
42
  # The domainkit.testing pytest plugin; Starlette's TestClient needs httpx2.
41
43
  testing = ["pytest (>=9.1.1,<10.0.0)", "httpx2 (>=2.13.0,<3.0.0)"]
42
44
 
@@ -68,6 +70,8 @@ dev = [
68
70
  "pip-audit (>=2.9.0,<3.0.0)",
69
71
  # Starlette's TestClient (it deprecates plain httpx).
70
72
  "httpx2 (>=2.13.0,<3.0.0)",
73
+ # An in-memory Redis, with Lua, so the Redis tests also run without a server.
74
+ "fakeredis[lua] (>=2.38.0,<3.0.0)",
71
75
  # In-memory span exporter for the tracing tests.
72
76
  "opentelemetry-sdk (>=1.45.1,<2.0.0)",
73
77
  # Random test order, property-based tests, mutation testing.
@@ -126,6 +130,8 @@ source = ["src"]
126
130
  [tool.coverage.report]
127
131
  show_missing = true
128
132
  skip_empty = true
133
+ # Every run, with or without servers; CI's coverage job also requires 100%
134
+ # of all runs combined.
129
135
  fail_under = 98
130
136
  precision = 2
131
137
  exclude_also = ["if TYPE_CHECKING:", "raise NotImplementedError"]
@@ -35,6 +35,7 @@ FORBIDDEN_LIBRARIES: Final = {
35
35
  "starlette",
36
36
  "sqlalchemy",
37
37
  "alembic",
38
+ "redis",
38
39
  "domainkit.db",
39
40
  "domainkit.http",
40
41
  ),
@@ -0,0 +1,39 @@
1
+ """Caches: in process memory, or in Redis or Valkey shared by every replica.
2
+
3
+ Configure them in ``CACHES`` (``default`` is a memory cache unless set),
4
+ then use them anywhere in the application layer::
5
+
6
+ from domainkit.cache import cached, caches
7
+
8
+ summary = await caches["default"].get_or_set(
9
+ f"invoice:{invoice_id}", lambda: load_summary(invoice_id), InvoiceSummary
10
+ )
11
+
12
+ @cached(ttl=60)
13
+ async def invoice_summary(invoice_id: UUID) -> InvoiceSummary: ...
14
+
15
+ Values are JSON; a failing cache is skipped, not fatal. See ADR 0012 and
16
+ ``docs/cache.md``.
17
+ """
18
+
19
+ from domainkit.cache.base import CacheError, CacheUnavailable, Store
20
+ from domainkit.cache.core import DEFAULT_TTL, TTL, Cache
21
+ from domainkit.cache.decorators import CachedFunction, cached, invalidate_on
22
+ from domainkit.cache.handler import CacheHandler, caches
23
+ from domainkit.cache.memory import DummyStore, MemoryStore
24
+
25
+ __all__ = [
26
+ "DEFAULT_TTL",
27
+ "TTL",
28
+ "Cache",
29
+ "CacheError",
30
+ "CacheHandler",
31
+ "CacheUnavailable",
32
+ "CachedFunction",
33
+ "DummyStore",
34
+ "MemoryStore",
35
+ "Store",
36
+ "cached",
37
+ "caches",
38
+ "invalidate_on",
39
+ ]
@@ -0,0 +1,63 @@
1
+ """The store port: what a cache backend implements.
2
+
3
+ A store keeps bytes under full keys, with expiry in milliseconds. ``Cache``
4
+ (``domainkit.cache.core``) builds keys, encodes values, and handles failures
5
+ on top of it, so a new backend implements only these operations.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from collections.abc import Mapping, Sequence
10
+
11
+
12
+ class CacheError(Exception):
13
+ """Base class of cache errors."""
14
+
15
+
16
+ class CacheUnavailable(CacheError):
17
+ """The cache could not be reached in time, or is skipped after a failure."""
18
+
19
+
20
+ class Store(ABC):
21
+ """Bytes under full keys; ``ttl`` is in milliseconds, None keeps the entry.
22
+
23
+ Raise ``CacheUnavailable`` when the backend cannot be reached, and
24
+ ``CacheError`` when it refuses an operation.
25
+ """
26
+
27
+ @abstractmethod
28
+ async def get_many(self, keys: Sequence[str]) -> list[bytes | None]:
29
+ """Return the value of each key, or None where it is missing or expired."""
30
+
31
+ @abstractmethod
32
+ async def set_many(self, items: Mapping[str, bytes], ttl: int | None) -> None:
33
+ """Store every item, replacing existing values."""
34
+
35
+ @abstractmethod
36
+ async def add(self, key: str, value: bytes, ttl: int | None) -> bool:
37
+ """Store ``value`` only if ``key`` is missing; report whether it was."""
38
+
39
+ @abstractmethod
40
+ async def delete_many(self, keys: Sequence[str]) -> int:
41
+ """Delete the keys; return how many existed."""
42
+
43
+ @abstractmethod
44
+ async def incr(self, key: str, delta: int, ttl: int | None) -> int:
45
+ """Add ``delta`` to an integer value and return it.
46
+
47
+ A missing key starts at 0 and gets ``ttl``; an existing key keeps its
48
+ expiry. Raise ``CacheError`` if the value is not an integer.
49
+ """
50
+
51
+ @abstractmethod
52
+ async def touch(self, key: str, ttl: int | None) -> bool:
53
+ """Give ``key`` a new expiry (None: none); report whether it exists."""
54
+
55
+ @abstractmethod
56
+ async def clear(self, prefix: str) -> int:
57
+ """Delete every key starting with ``prefix``; return how many."""
58
+
59
+ async def check(self) -> None: # noqa: B027 - optional; most stores have it.
60
+ """Raise if the backend cannot be reached (readiness checks)."""
61
+
62
+ async def close(self) -> None: # noqa: B027 - optional, for connections.
63
+ """Release connections; the store is not used afterwards."""