""" Multi-tenancy Support Support for multiple organizations on the same platform """ from typing import Dict, List, Optional from dataclasses import dataclass from datetime import datetime from enum import Enum class TenantTier(str, Enum): """Tenant subscription tiers""" FREE = "free" BASIC = "basic" PROFESSIONAL = "professional" ENTERPRISE = "enterprise" @dataclass class Tenant: """Organization tenant""" id: str name: str tier: TenantTier created_at: str settings: Dict limits: Dict features: List[str] active: bool def to_dict(self) -> Dict: return { "id": self.id, "name": self.name, "tier": self.tier.value, "created_at": self.created_at, "settings": self.settings, "limits": self.limits, "features": self.features, "active": self.active } class TenantManager: """ Multi-tenancy manager Features: - Tenant isolation - Resource limits - Feature flags - Custom branding - Data segregation """ def __init__(self): self.tenants: Dict[str, Tenant] = {} self.default_limits = { TenantTier.FREE: { "max_observations": 100, "max_users": 1, "max_objects": 50, "max_storage_mb": 100, "api_calls_per_day": 1000, "features": ["basic_observations", "basic_reports"] }, TenantTier.BASIC: { "max_observations": 1000, "max_users": 5, "max_objects": 500, "max_storage_mb": 1000, "api_calls_per_day": 10000, "features": ["basic_observations", "basic_reports", "api_access", "email_support"] }, TenantTier.PROFESSIONAL: { "max_observations": 10000, "max_users": 25, "max_objects": 5000, "max_storage_mb": 10000, "api_calls_per_day": 100000, "features": ["advanced_observations", "advanced_reports", "api_access", "priority_support", "custom_branding", "webhooks"] }, TenantTier.ENTERPRISE: { "max_observations": -1, # Unlimited "max_users": -1, "max_objects": -1, "max_storage_mb": -1, "api_calls_per_day": -1, "features": ["all_features", "dedicated_support", "sla", "custom_development", "on_premise"] } } def create_tenant( self, name: str, tier: TenantTier = TenantTier.FREE, settings: Optional[Dict] = None ) -> Tenant: """Create new tenant""" tenant_id = f"TEN-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}" limits = self.default_limits[tier].copy() features = limits.pop("features", []) tenant = Tenant( id=tenant_id, name=name, tier=tier, created_at=datetime.utcnow().isoformat(), settings=settings or {}, limits=limits, features=features, active=True ) self.tenants[tenant_id] = tenant return tenant def get_tenant(self, tenant_id: str) -> Optional[Tenant]: """Get tenant by ID""" return self.tenants.get(tenant_id) def update_tenant(self, tenant_id: str, updates: Dict) -> Optional[Tenant]: """Update tenant""" tenant = self.tenants.get(tenant_id) if not tenant: return None if "name" in updates: tenant.name = updates["name"] if "tier" in updates: tenant.tier = TenantTier(updates["tier"]) # Update limits and features limits = self.default_limits[tenant.tier].copy() tenant.features = limits.pop("features", []) tenant.limits.update(limits) if "settings" in updates: tenant.settings.update(updates["settings"]) if "active" in updates: tenant.active = updates["active"] return tenant def delete_tenant(self, tenant_id: str) -> bool: """Delete tenant (soft delete)""" tenant = self.tenants.get(tenant_id) if tenant: tenant.active = False return True return False def check_limit(self, tenant_id: str, limit_type: str, current_value: int) -> bool: """Check if tenant is within limits""" tenant = self.tenants.get(tenant_id) if not tenant: return False limit = tenant.limits.get(limit_type, 0) if limit == -1: # Unlimited return True return current_value < limit def has_feature(self, tenant_id: str, feature: str) -> bool: """Check if tenant has feature""" tenant = self.tenants.get(tenant_id) if not tenant: return False return feature in tenant.features or "all_features" in tenant.features def get_tenant_stats(self, tenant_id: str) -> Dict: """Get tenant statistics""" tenant = self.tenants.get(tenant_id) if not tenant: return {} return { "tenant": tenant.to_dict(), "usage": { "observations": 0, # In production, query database "users": 0, "objects": 0, "storage_mb": 0 }, "limits_remaining": { k: v - 0 if v > 0 else "unlimited" for k, v in tenant.limits.items() } } def list_tenants(self, active_only: bool = True) -> List[Dict]: """List all tenants""" tenants = self.tenants.values() if active_only: tenants = [t for t in tenants if t.active] return [t.to_dict() for t in tenants] # Example usage def example_multi_tenancy(): """Example: Multi-tenancy""" print("=== Multi-Tenancy Demo ===\n") manager = TenantManager() # Create tenants print("Creating tenants...") free_tenant = manager.create_tenant( name="Small Municipality", tier=TenantTier.FREE ) print(f"Free tenant: {free_tenant.name} ({free_tenant.id})") print(f" Limits: {free_tenant.limits}") pro_tenant = manager.create_tenant( name="Large City", tier=TenantTier.PROFESSIONAL, settings={"custom_domain": "city.iom.app"} ) print(f"Pro tenant: {pro_tenant.name} ({pro_tenant.id})") print(f" Features: {pro_tenant.features}") enterprise_tenant = manager.create_tenant( name="National Agency", tier=TenantTier.ENTERPRISE ) print(f"Enterprise tenant: {enterprise_tenant.name} ({enterprise_tenant.id})") # Check limits print("\nChecking limits...") print(f"Free tenant can create observation (0/100): {manager.check_limit(free_tenant.id, 'max_observations', 0)}") print(f"Free tenant can create observation (100/100): {manager.check_limit(free_tenant.id, 'max_observations', 100)}") print(f"Enterprise tenant unlimited: {manager.check_limit(enterprise_tenant.id, 'max_observations', 999999)}") # Check features print("\nChecking features...") print(f"Free has API access: {manager.has_feature(free_tenant.id, 'api_access')}") print(f"Pro has API access: {manager.has_feature(pro_tenant.id, 'api_access')}") print(f"Pro has custom branding: {manager.has_feature(pro_tenant.id, 'custom_branding')}") # List tenants print("\nAll tenants:") for tenant in manager.list_tenants(): print(f" {tenant['name']} ({tenant['tier']})") return manager if __name__ == '__main__': example_multi_tenancy()