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.
- {domainkit-0.3.0 → domainkit-0.4.0}/PKG-INFO +25 -9
- {domainkit-0.3.0 → domainkit-0.4.0}/README.md +22 -8
- {domainkit-0.3.0 → domainkit-0.4.0}/pyproject.toml +7 -1
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/architecture.py +1 -0
- domainkit-0.4.0/src/domainkit/cache/__init__.py +39 -0
- domainkit-0.4.0/src/domainkit/cache/base.py +63 -0
- domainkit-0.4.0/src/domainkit/cache/core.py +400 -0
- domainkit-0.4.0/src/domainkit/cache/decorators.py +126 -0
- domainkit-0.4.0/src/domainkit/cache/handler.py +122 -0
- domainkit-0.4.0/src/domainkit/cache/memory.py +155 -0
- domainkit-0.4.0/src/domainkit/cache/metrics.py +24 -0
- domainkit-0.4.0/src/domainkit/cache/redis.py +176 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/__init__.py +4 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/global_settings.py +123 -1
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/application.py +3 -1
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/health.py +2 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/ratelimit.py +60 -2
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/base.py +2 -0
- domainkit-0.4.0/src/domainkit/management/commands/cache.py +48 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/testing.py +10 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/LICENSE +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/apps/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/apps/config.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/apps/registry.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/env.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/conf/lazy.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/context.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/apps.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/management/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/management/commands/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/management/commands/auditlog.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/protection.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/recorder.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/settings.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/audit/tables.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/apps.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/management/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/management/commands/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/management/commands/evidence.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/model.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/settings.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/store.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/evidence/tasks.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/apps.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/middleware.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/settings.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/tables.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/idempotency/tasks.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/admin.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/apps.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/commands/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/commands/messages.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/management/commands/runworker.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/metrics.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/outbox.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/registry.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/settings.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/tables.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/contrib/messaging/worker.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/connections.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/drivers.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migration_env/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migration_env/env.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migration_env/script.py.mako +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/migrations.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/orm.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/partitioning.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/repository.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/routing.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/types.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/db/uow.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/_dataclass.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/events.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/exceptions.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/messages.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/model.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/pagination.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/repository.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ddd/uow.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/deprecation.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/exceptions.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/errors.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/metrics.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/middleware.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/http/pagination.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/base.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/handler.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/immudb.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/ledger/memory.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/log.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/check.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/init.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/makemigrations.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/migrate.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/partitions.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/runserver.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/shell.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/showmigrations.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/commands/startdomain.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/dependencies.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/routes.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/api/schemas.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/commands.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/handlers.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/services.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/application/unit_of_work.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/apps.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/events.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/exceptions.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/model.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/domain/repositories.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/orm.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/repositories.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/infrastructure/unit_of_work.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/management/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/management/commands/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/management/commands/{{ aggregates }}.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/package/settings.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/fakes.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_api.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_model.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_services.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/domain/tests/test_settings.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/dot-env-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/dot-gitignore-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/manage.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/pyproject.toml-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/asgi.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/base.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/local.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/production.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/settings/test.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/config/urls.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/src/domains/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/tests/__init__.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/management/templates/project/tests/test_health.py-tpl +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/py.typed +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/reporting.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/tracing.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/__init__.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/formatting.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/module_loading.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/rates.py +0 -0
- {domainkit-0.3.0 → domainkit-0.4.0}/src/domainkit/utils/readiness.py +0 -0
- {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
|
+
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
132
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
|
89
|
-
|
|
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
|
+
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"]
|
|
@@ -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."""
|