Source code for src.module.tenant.domain.services
"""Tenant Context domain services.
Domain services contain business logic that doesn't naturally belong to a single
aggregate. They receive dependencies via parameters (no direct imports from infra).
"""
from __future__ import annotations
from typing import Any
[docs]
def provision_tenant(
tenant: Any,
isolation_backend: Any,
event_publisher: Any,
) -> None:
"""Provision storage for a new tenant and emit TenantCreated event.
Args:
tenant: The Tenant model instance.
isolation_backend: The active TenantIsolationBackend.
event_publisher: Callable to publish domain events.
"""
from src.module.tenant.domain.events import TenantCreated
isolation_backend.create_tenant_storage(tenant)
event_publisher(
TenantCreated(
tenant_id=str(tenant.id),
tenant_slug=tenant.slug,
schema_name=tenant.schema_name,
)
)
[docs]
def resolve_tenant_from_identifier(
identifier: str,
tenant_queryset: Any,
) -> Any:
"""Resolve a tenant by slug or UUID from the given queryset.
Args:
identifier: Tenant slug or UUID string.
tenant_queryset: A queryset-like object with .by_slug() and .by_id() methods.
Returns:
The resolved Tenant instance.
Raises:
TenantNotFoundError: If the tenant cannot be found.
TenantInactiveError: If the tenant exists but is not active.
"""
import uuid
from src.share_kernel.domain.exceptions import TenantInactiveError, TenantNotFoundError
tenant = None
# Try UUID first
try:
tenant_uuid = uuid.UUID(identifier)
tenant = tenant_queryset.filter(id=tenant_uuid).first()
except (ValueError, AttributeError):
pass
# Fall back to slug
if tenant is None:
tenant = tenant_queryset.filter(slug=identifier).first()
if tenant is None:
raise TenantNotFoundError(f"Tenant not found: {identifier}")
if tenant.status != "active":
raise TenantInactiveError(f"Tenant '{tenant.slug}' is {tenant.status}")
return tenant
[docs]
def validate_membership(
user_id: str,
tenant_id: str,
membership_queryset: Any,
) -> Any:
"""Validate that a user has an active membership in a tenant.
Args:
user_id: The user's UUID string.
tenant_id: The tenant's UUID string.
membership_queryset: A queryset for TenantMembership lookups.
Returns:
The active TenantMembership instance.
Raises:
MembershipNotFoundError: If no active membership exists.
"""
from src.share_kernel.domain.exceptions import MembershipNotFoundError
membership = membership_queryset.filter(
user_id=user_id,
tenant_id=tenant_id,
status="active",
).first()
if membership is None:
raise MembershipNotFoundError(
f"User {user_id} has no active membership in tenant {tenant_id}"
)
return membership