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.
- {domainkit-0.6.2 → domainkit-0.8.0}/PKG-INFO +61 -8
- {domainkit-0.6.2 → domainkit-0.8.0}/README.md +45 -7
- {domainkit-0.6.2 → domainkit-0.8.0}/pyproject.toml +21 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/__init__.py +1 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/apps/config.py +23 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/apps/registry.py +24 -5
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/architecture.py +4 -4
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/base.py +7 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/handler.py +29 -5
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/memory.py +45 -24
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/rabbitmq.py +128 -46
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/__init__.py +6 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/core.py +14 -5
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/memory.py +2 -2
- domainkit-0.8.0/src/domainkit/cache/pages.py +236 -0
- domainkit-0.8.0/src/domainkit/cache/redis.py +375 -0
- domainkit-0.8.0/src/domainkit/clock.py +198 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/__init__.py +2 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/global_settings.py +403 -26
- domainkit-0.8.0/src/domainkit/contrib/admin/__init__.py +62 -0
- domainkit-0.8.0/src/domainkit/contrib/admin/sites.py +588 -0
- domainkit-0.8.0/src/domainkit/contrib/audit/api.py +148 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/apps.py +8 -2
- domainkit-0.8.0/src/domainkit/contrib/audit/history.py +206 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/auditforget.py +3 -2
- domainkit-0.8.0/src/domainkit/contrib/audit/management/commands/auditlog.py +101 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/auditseal.py +15 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/auditverify.py +2 -0
- domainkit-0.8.0/src/domainkit/contrib/audit/models.py +21 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/personal.py +17 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/recorder.py +2 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/seals.py +256 -52
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/settings.py +11 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/tables.py +15 -4
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/__init__.py +8 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/access.py +89 -16
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/admin.py +68 -96
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/api.py +82 -94
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/apps.py +154 -7
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/authentication.py +49 -11
- domainkit-0.8.0/src/domainkit/contrib/auth/certificates.py +188 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/cookies.py +384 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/erasure.py +44 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/events.py +59 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/exceptions.py +4 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/hashers.py +97 -26
- domainkit-0.8.0/src/domainkit/contrib/auth/management/commands/addcertificate.py +51 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/createsuperuser.py +7 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/createtoken.py +41 -5
- domainkit-0.8.0/src/domainkit/contrib/auth/management/commands/resetmfa.py +42 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/mfa.py +479 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/middleware.py +67 -17
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/models.py +163 -15
- domainkit-0.8.0/src/domainkit/contrib/auth/onetime.py +119 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/personal_tokens.py +220 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/recovery.py +352 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/repositories.py +745 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/settings.py +297 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/signup.py +286 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/tables.py +75 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/tasks.py +6 -2
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/email_verification.subject.txt +1 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/email_verification.txt +7 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/password_reset.subject.txt +1 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/password_reset.txt +8 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_confirm.subject.txt +1 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_confirm.txt +8 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_exists.subject.txt +1 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/templates/mail/auth/signup_exists.txt +7 -0
- domainkit-0.8.0/src/domainkit/contrib/auth/throttling.py +124 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/tokens.py +13 -2
- domainkit-0.8.0/src/domainkit/contrib/auth/totp.py +84 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/uow.py +12 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/model.py +2 -1
- domainkit-0.8.0/src/domainkit/contrib/flags/__init__.py +55 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/admin.py +225 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/apps.py +32 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/dependencies.py +28 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/events.py +32 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/exceptions.py +15 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/management/__init__.py +1 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/management/commands/__init__.py +1 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/management/commands/flags.py +161 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/models.py +113 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/settings.py +18 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/store.py +208 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/tables.py +38 -0
- domainkit-0.8.0/src/domainkit/contrib/flags/uow.py +40 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/apps.py +5 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/middleware.py +98 -20
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/operations.py +13 -6
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/settings.py +2 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/tables.py +6 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/tasks.py +3 -4
- domainkit-0.8.0/src/domainkit/contrib/mail/__init__.py +56 -0
- domainkit-0.8.0/src/domainkit/contrib/mail/apps.py +43 -0
- domainkit-0.8.0/src/domainkit/contrib/mail/backends.py +173 -0
- domainkit-0.8.0/src/domainkit/contrib/mail/delivery.py +113 -0
- domainkit-0.8.0/src/domainkit/contrib/mail/message.py +204 -0
- domainkit-0.8.0/src/domainkit/contrib/mail/settings.py +76 -0
- domainkit-0.8.0/src/domainkit/contrib/mail/templates.py +157 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/__init__.py +24 -5
- domainkit-0.8.0/src/domainkit/contrib/messaging/admin.py +191 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/apps.py +16 -13
- domainkit-0.8.0/src/domainkit/contrib/messaging/breaker.py +157 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/budget.py +102 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/cron.py +272 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/dbclock.py +46 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/dispatch.py +559 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/management/commands/messages.py +125 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/management/commands/runworker.py +22 -6
- domainkit-0.8.0/src/domainkit/contrib/messaging/metrics.py +151 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/outbox.py +265 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/registry.py +399 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/retry.py +90 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/settings.py +27 -12
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/tables.py +17 -1
- domainkit-0.8.0/src/domainkit/contrib/messaging/testing.py +401 -0
- domainkit-0.8.0/src/domainkit/contrib/messaging/worker.py +1186 -0
- domainkit-0.8.0/src/domainkit/contrib/notifications/__init__.py +65 -0
- domainkit-0.8.0/src/domainkit/contrib/notifications/apps.py +60 -0
- domainkit-0.8.0/src/domainkit/contrib/notifications/backends.py +107 -0
- domainkit-0.8.0/src/domainkit/contrib/notifications/delivery.py +134 -0
- domainkit-0.8.0/src/domainkit/contrib/notifications/messages.py +107 -0
- domainkit-0.8.0/src/domainkit/contrib/notifications/settings.py +24 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/__init__.py +63 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/apps.py +25 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/backends.py +34 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/criteria.py +178 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/deletions.py +47 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/models.py +146 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/services.py +185 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/tables.py +50 -0
- domainkit-0.8.0/src/domainkit/contrib/objectpermissions/uow.py +88 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/__init__.py +55 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/apps.py +38 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/events.py +18 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/management/__init__.py +1 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/management/commands/__init__.py +1 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/management/commands/privacy.py +121 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/models.py +334 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/operations.py +423 -0
- domainkit-0.8.0/src/domainkit/contrib/privacy/retention.py +53 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/__init__.py +35 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/admin.py +403 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/apps.py +67 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/client.py +132 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/delivery.py +529 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/guard.py +92 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/management/__init__.py +1 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/management/commands/__init__.py +1 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/management/commands/webhooks.py +133 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/models.py +240 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/settings.py +50 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/signing.py +114 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/tables.py +89 -0
- domainkit-0.8.0/src/domainkit/contrib/webhooks/uow.py +43 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/__init__.py +19 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/connections.py +62 -17
- domainkit-0.8.0/src/domainkit/db/derive.py +282 -0
- domainkit-0.8.0/src/domainkit/db/drivers.py +282 -0
- domainkit-0.8.0/src/domainkit/db/errors.py +302 -0
- domainkit-0.8.0/src/domainkit/db/fixtures.py +392 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migrations.py +131 -7
- domainkit-0.8.0/src/domainkit/db/orm.py +102 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/partitioning.py +2 -3
- domainkit-0.8.0/src/domainkit/db/query.py +232 -0
- domainkit-0.8.0/src/domainkit/db/repository.py +182 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/routing.py +31 -1
- domainkit-0.8.0/src/domainkit/db/squashing.py +358 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/uow.py +52 -4
- domainkit-0.8.0/src/domainkit/ddd/__init__.py +87 -0
- domainkit-0.8.0/src/domainkit/ddd/events.py +207 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/exceptions.py +10 -0
- domainkit-0.8.0/src/domainkit/ddd/fields.py +341 -0
- domainkit-0.8.0/src/domainkit/ddd/messages.py +69 -0
- domainkit-0.8.0/src/domainkit/ddd/pagination.py +37 -0
- domainkit-0.8.0/src/domainkit/ddd/preconditions.py +111 -0
- domainkit-0.8.0/src/domainkit/ddd/query.py +804 -0
- domainkit-0.8.0/src/domainkit/ddd/repository.py +610 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/uow.py +101 -4
- domainkit-0.8.0/src/domainkit/deadlines.py +317 -0
- domainkit-0.8.0/src/domainkit/debug/__init__.py +11 -0
- domainkit-0.8.0/src/domainkit/debug/middleware.py +123 -0
- domainkit-0.8.0/src/domainkit/debug/queries.py +117 -0
- domainkit-0.8.0/src/domainkit/encryption.py +90 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/__init__.py +20 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/application.py +40 -6
- domainkit-0.8.0/src/domainkit/http/blocking.py +152 -0
- domainkit-0.8.0/src/domainkit/http/client.py +843 -0
- domainkit-0.8.0/src/domainkit/http/deadlines.py +141 -0
- domainkit-0.8.0/src/domainkit/http/errors.py +464 -0
- domainkit-0.8.0/src/domainkit/http/filters.py +430 -0
- domainkit-0.8.0/src/domainkit/http/guard.py +411 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/health.py +25 -7
- domainkit-0.8.0/src/domainkit/http/live.py +475 -0
- domainkit-0.8.0/src/domainkit/http/live_events.py +370 -0
- domainkit-0.8.0/src/domainkit/http/locale.py +51 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/metrics.py +36 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/middleware.py +107 -25
- domainkit-0.8.0/src/domainkit/http/openapi.py +551 -0
- domainkit-0.8.0/src/domainkit/http/pagination.py +561 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/http/ratelimit.py +219 -39
- domainkit-0.8.0/src/domainkit/http/servers.py +229 -0
- domainkit-0.8.0/src/domainkit/http/shedding.py +366 -0
- domainkit-0.8.0/src/domainkit/http/sse.py +341 -0
- domainkit-0.8.0/src/domainkit/http/uploads.py +298 -0
- domainkit-0.8.0/src/domainkit/http/versioning.py +204 -0
- domainkit-0.8.0/src/domainkit/http/websockets.py +297 -0
- domainkit-0.8.0/src/domainkit/i18n/__init__.py +68 -0
- domainkit-0.8.0/src/domainkit/i18n/catalogs.py +293 -0
- domainkit-0.8.0/src/domainkit/i18n/formats.py +308 -0
- domainkit-0.8.0/src/domainkit/i18n/locale/ar/LC_MESSAGES/messages.mo +0 -0
- domainkit-0.8.0/src/domainkit/i18n/locale/ar/LC_MESSAGES/messages.po +640 -0
- domainkit-0.8.0/src/domainkit/i18n/locale/en/LC_MESSAGES/messages.mo +0 -0
- domainkit-0.8.0/src/domainkit/i18n/locale/en/LC_MESSAGES/messages.po +634 -0
- domainkit-0.8.0/src/domainkit/i18n/timezone.py +90 -0
- domainkit-0.8.0/src/domainkit/i18n/translation.py +271 -0
- domainkit-0.8.0/src/domainkit/i18n/validation.py +54 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/__init__.py +2 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/base.py +15 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/handler.py +6 -2
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/immudb.py +169 -70
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ledger/memory.py +13 -2
- domainkit-0.8.0/src/domainkit/lifecycle.py +234 -0
- domainkit-0.8.0/src/domainkit/log.py +598 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/base.py +11 -4
- domainkit-0.8.0/src/domainkit/management/commands/addcommand.py +358 -0
- domainkit-0.8.0/src/domainkit/management/commands/addevent.py +197 -0
- domainkit-0.8.0/src/domainkit/management/commands/addquery.py +289 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/check.py +19 -2
- domainkit-0.8.0/src/domainkit/management/commands/compilemessages.py +101 -0
- domainkit-0.8.0/src/domainkit/management/commands/dbshell.py +140 -0
- domainkit-0.8.0/src/domainkit/management/commands/diffsettings.py +67 -0
- domainkit-0.8.0/src/domainkit/management/commands/dumpdata.py +86 -0
- domainkit-0.8.0/src/domainkit/management/commands/flush.py +57 -0
- domainkit-0.8.0/src/domainkit/management/commands/loaddata.py +90 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/makemigrations.py +25 -1
- domainkit-0.8.0/src/domainkit/management/commands/migrate.py +147 -0
- domainkit-0.8.0/src/domainkit/management/commands/openapi.py +111 -0
- domainkit-0.8.0/src/domainkit/management/commands/runserver.py +109 -0
- domainkit-0.8.0/src/domainkit/management/commands/seed.py +39 -0
- domainkit-0.8.0/src/domainkit/management/commands/shell.py +147 -0
- domainkit-0.8.0/src/domainkit/management/commands/squashmigrations.py +57 -0
- domainkit-0.8.0/src/domainkit/management/commands/startaggregate.py +399 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/startdomain.py +40 -52
- domainkit-0.8.0/src/domainkit/management/generators.py +832 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/__init__.py-tpl +2 -2
- domainkit-0.8.0/src/domainkit/management/templates/domain/package/api/routes.py-tpl +89 -0
- domainkit-0.8.0/src/domainkit/management/templates/domain/package/application/commands.py-tpl +38 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/application/handlers.py-tpl +0 -1
- domainkit-0.8.0/src/domainkit/management/templates/domain/package/application/services.py-tpl +62 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/application/unit_of_work.py-tpl +1 -1
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/apps.py-tpl +0 -3
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/domain/events.py-tpl +13 -0
- domainkit-0.8.0/src/domainkit/management/templates/domain/package/domain/model.py-tpl +122 -0
- domainkit-0.8.0/src/domainkit/management/templates/domain/package/infrastructure/orm.py-tpl +44 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/infrastructure/unit_of_work.py-tpl +11 -3
- domainkit-0.8.0/src/domainkit/management/templates/domain/tests/fakes.py-tpl +54 -0
- domainkit-0.8.0/src/domainkit/management/templates/domain/tests/test_api.py-tpl +110 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/tests/test_model.py-tpl +52 -11
- domainkit-0.8.0/src/domainkit/management/templates/domain/tests/test_services.py-tpl +98 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/pyproject.toml-tpl +7 -1
- domainkit-0.8.0/src/domainkit/management/templates/project/src/config/settings/base.py-tpl +44 -0
- domainkit-0.8.0/src/domainkit/management/templates/project/src/config/settings/local.py-tpl +16 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/settings/production.py-tpl +16 -6
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/settings/test.py-tpl +1 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/tests/test_health.py-tpl +9 -1
- domainkit-0.8.0/src/domainkit/reporting.py +176 -0
- domainkit-0.8.0/src/domainkit/resources/__init__.py +59 -0
- domainkit-0.8.0/src/domainkit/resources/access.py +102 -0
- domainkit-0.8.0/src/domainkit/resources/batches.py +121 -0
- domainkit-0.8.0/src/domainkit/resources/conditional.py +64 -0
- domainkit-0.8.0/src/domainkit/resources/events.py +34 -0
- domainkit-0.8.0/src/domainkit/resources/policies.py +168 -0
- domainkit-0.8.0/src/domainkit/resources/renderers.py +174 -0
- domainkit-0.8.0/src/domainkit/resources/routing.py +1788 -0
- domainkit-0.8.0/src/domainkit/resources/schemas.py +337 -0
- domainkit-0.8.0/src/domainkit/signing.py +314 -0
- domainkit-0.8.0/src/domainkit/storage/__init__.py +43 -0
- domainkit-0.8.0/src/domainkit/storage/base.py +226 -0
- domainkit-0.8.0/src/domainkit/storage/filesystem.py +271 -0
- domainkit-0.8.0/src/domainkit/storage/handler.py +60 -0
- domainkit-0.8.0/src/domainkit/storage/memory.py +63 -0
- domainkit-0.8.0/src/domainkit/tasks.py +265 -0
- domainkit-0.8.0/src/domainkit/testing/__init__.py +238 -0
- domainkit-0.8.0/src/domainkit/testing/blocking.py +128 -0
- domainkit-0.8.0/src/domainkit/testing/clients.py +274 -0
- domainkit-0.8.0/src/domainkit/testing/databases.py +259 -0
- domainkit-0.8.0/src/domainkit/testing/factories.py +313 -0
- domainkit-0.8.0/src/domainkit/testing/queries.py +131 -0
- domainkit-0.8.0/src/domainkit/tracing.py +150 -0
- domainkit-0.8.0/src/domainkit/utils/closing.py +124 -0
- domainkit-0.8.0/src/domainkit/utils/headers.py +85 -0
- domainkit-0.8.0/src/domainkit/utils/loops.py +57 -0
- domainkit-0.8.0/src/domainkit/utils/redis_urls.py +143 -0
- domainkit-0.6.2/src/domainkit/cache/redis.py +0 -176
- domainkit-0.6.2/src/domainkit/contrib/audit/management/commands/auditlog.py +0 -53
- domainkit-0.6.2/src/domainkit/contrib/auth/repositories.py +0 -361
- domainkit-0.6.2/src/domainkit/contrib/auth/settings.py +0 -137
- domainkit-0.6.2/src/domainkit/contrib/messaging/admin.py +0 -62
- domainkit-0.6.2/src/domainkit/contrib/messaging/dispatch.py +0 -316
- domainkit-0.6.2/src/domainkit/contrib/messaging/management/commands/messages.py +0 -58
- domainkit-0.6.2/src/domainkit/contrib/messaging/metrics.py +0 -57
- domainkit-0.6.2/src/domainkit/contrib/messaging/outbox.py +0 -161
- domainkit-0.6.2/src/domainkit/contrib/messaging/registry.py +0 -215
- domainkit-0.6.2/src/domainkit/contrib/messaging/worker.py +0 -654
- domainkit-0.6.2/src/domainkit/db/drivers.py +0 -160
- domainkit-0.6.2/src/domainkit/db/errors.py +0 -112
- domainkit-0.6.2/src/domainkit/db/orm.py +0 -44
- domainkit-0.6.2/src/domainkit/db/repository.py +0 -33
- domainkit-0.6.2/src/domainkit/ddd/__init__.py +0 -48
- domainkit-0.6.2/src/domainkit/ddd/events.py +0 -127
- domainkit-0.6.2/src/domainkit/ddd/messages.py +0 -16
- domainkit-0.6.2/src/domainkit/ddd/pagination.py +0 -24
- domainkit-0.6.2/src/domainkit/ddd/repository.py +0 -94
- domainkit-0.6.2/src/domainkit/http/errors.py +0 -234
- domainkit-0.6.2/src/domainkit/http/pagination.py +0 -71
- domainkit-0.6.2/src/domainkit/lifecycle.py +0 -38
- domainkit-0.6.2/src/domainkit/log.py +0 -119
- domainkit-0.6.2/src/domainkit/management/commands/migrate.py +0 -88
- domainkit-0.6.2/src/domainkit/management/commands/runserver.py +0 -60
- domainkit-0.6.2/src/domainkit/management/commands/shell.py +0 -27
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/api/dependencies.py-tpl +0 -47
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/api/routes.py-tpl +0 -102
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/api/schemas.py-tpl +0 -65
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/application/commands.py-tpl +0 -24
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/application/services.py-tpl +0 -63
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/domain/exceptions.py-tpl +0 -15
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/domain/model.py-tpl +0 -71
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/domain/repositories.py-tpl +0 -33
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/infrastructure/orm.py-tpl +0 -46
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/infrastructure/repositories.py-tpl +0 -26
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/management/__init__.py-tpl +0 -1
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/management/commands/__init__.py-tpl +0 -1
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/management/commands/{{ aggregates }}.py-tpl +0 -25
- domainkit-0.6.2/src/domainkit/management/templates/domain/package/settings.py-tpl +0 -30
- domainkit-0.6.2/src/domainkit/management/templates/domain/tests/fakes.py-tpl +0 -47
- domainkit-0.6.2/src/domainkit/management/templates/domain/tests/test_api.py-tpl +0 -121
- domainkit-0.6.2/src/domainkit/management/templates/domain/tests/test_services.py-tpl +0 -71
- domainkit-0.6.2/src/domainkit/management/templates/domain/tests/test_settings.py-tpl +0 -20
- domainkit-0.6.2/src/domainkit/management/templates/project/src/config/settings/base.py-tpl +0 -27
- domainkit-0.6.2/src/domainkit/management/templates/project/src/config/settings/local.py-tpl +0 -11
- domainkit-0.6.2/src/domainkit/reporting.py +0 -50
- domainkit-0.6.2/src/domainkit/testing.py +0 -179
- domainkit-0.6.2/src/domainkit/tracing.py +0 -42
- domainkit-0.6.2/src/domainkit/utils/closing.py +0 -26
- {domainkit-0.6.2 → domainkit-0.8.0}/LICENSE +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/apps/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/broker/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/base.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/decorators.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/handler.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/cache/metrics.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/env.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/conf/lazy.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/context.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/management/commands/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/protection.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/audit/tasks.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/backends.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/common-passwords.NOTICE +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/common-passwords.txt.gz +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/changepassword.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/management/commands/cleartokens.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/permissions.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/users.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/auth/validators.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/apps.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/models.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/registry.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/sync.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/contenttypes/tables.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/apps.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/management/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/management/commands/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/management/commands/evidence.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/settings.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/store.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/evidence/tasks.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/idempotency/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/management/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/contrib/messaging/management/commands/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migration_env/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migration_env/env.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/migration_env/script.py.mako +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/snapshots.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/db/types.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/_dataclass.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/model.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/ddd/personal.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/deprecation.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/exceptions.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/broker.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/cache.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/init.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/partitions.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/commands/showmigrations.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/api/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/application/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/domain/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/package/infrastructure/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/domain/tests/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/dot-env-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/dot-gitignore-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/manage.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/asgi.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/settings/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/config/urls.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/src/domains/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/management/templates/project/tests/__init__.py-tpl +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/py.typed +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/__init__.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/formatting.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/module_loading.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/rates.py +0 -0
- {domainkit-0.6.2 → domainkit-0.8.0}/src/domainkit/utils/readiness.py +0 -0
- {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.
|
|
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
|
|
62
|
-
rate limits shared by every
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 #
|
|
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
|
|
11
|
-
rate limits shared by every
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 #
|
|
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.
|
|
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"
|
|
@@ -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
|
|
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(
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
77
|
-
|
|
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
|
|
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
|
|
18
|
+
"""The pump that hands queued messages to a handler."""
|
|
18
19
|
|
|
19
|
-
def __init__(self,
|
|
20
|
-
"""Own ``
|
|
21
|
-
self.
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
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,
|
|
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,
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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,
|
|
71
|
+
self, queues: Sequence[str], slots: asyncio.Semaphore, handler: Handler
|
|
67
72
|
) -> None:
|
|
68
|
-
"""Hand out
|
|
69
|
-
|
|
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
|
|
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.
|
|
132
|
+
self._put(queue, (body, message_id))
|
|
112
133
|
|
|
113
134
|
await handler(Delivery(queue, body, message_id, ack, requeue))
|