""" Domain models for space composability. Defines models for space references, inheritance, and access control. """ import uuid from dataclasses import dataclass, field from datetime import datetime from typing import Dict, Any, List, Optional from enum import Enum class SpaceReferenceType(Enum): """Type of reference between spaces.""" INCLUDES = "includes" # Space A includes content from Space B EXTENDS = "extends" # Space A extends/inherits from Space B LINKS_TO = "links_to" # Space A has a soft link to Space B COMPOSED_OF = "composed_of" # Space A is composed of multiple spaces @dataclass class SpaceReference: """ Represents a reference between two spaces. Unlike parent_space_id (single inheritance), space references allow multiple relationships between spaces for composition. Attributes: id: Unique reference identifier source_space_id: The space making the reference target_space_id: The space being referenced reference_type: Type of reference relationship alias: Optional alias for the referenced space metadata: Additional reference metadata created_at: When the reference was created """ id: str = field(default_factory=lambda: str(uuid.uuid4())) source_space_id: str = "" target_space_id: str = "" reference_type: SpaceReferenceType = SpaceReferenceType.LINKS_TO alias: Optional[str] = None metadata: Dict[str, Any] = field(default_factory=dict) created_at: datetime = field(default_factory=datetime.now) def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for serialization.""" return { "id": self.id, "source_space_id": self.source_space_id, "target_space_id": self.target_space_id, "reference_type": self.reference_type.value, "alias": self.alias, "metadata": self.metadata, "created_at": self.created_at.isoformat(), } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "SpaceReference": """Create from dictionary.""" created_at = data.get("created_at") if isinstance(created_at, str): created_at = datetime.fromisoformat(created_at) elif created_at is None: created_at = datetime.now() ref_type = data.get("reference_type", "links_to") if isinstance(ref_type, str): ref_type = SpaceReferenceType(ref_type) return cls( id=data.get("id", str(uuid.uuid4())), source_space_id=data.get("source_space_id", ""), target_space_id=data.get("target_space_id", ""), reference_type=ref_type, alias=data.get("alias"), metadata=data.get("metadata", {}), created_at=created_at, ) class AccessLevel(Enum): """Access level for space permissions.""" NONE = "none" # No access READ = "read" # Read-only access WRITE = "write" # Read and write access ADMIN = "admin" # Full administrative access class SpaceRole(Enum): """Predefined roles for space access.""" OWNER = "owner" # Full control, can delete space ADMIN = "admin" # Can manage members and settings EDITOR = "editor" # Can edit documents and variables VIEWER = "viewer" # Read-only access GUEST = "guest" # Limited read access @dataclass class SpacePermission: """ Permission entry for a space. Maps a principal (user/group/role) to an access level for a space. Attributes: space_id: The space this permission applies to principal_type: Type of principal (user, group, role) principal_id: Identifier for the principal access_level: The access level granted role: Optional predefined role granted_by: Who granted this permission granted_at: When permission was granted expires_at: Optional expiration timestamp """ space_id: str principal_type: str # "user", "group", "role" principal_id: str access_level: AccessLevel = AccessLevel.READ role: Optional[SpaceRole] = None granted_by: Optional[str] = None granted_at: datetime = field(default_factory=datetime.now) expires_at: Optional[datetime] = None def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "space_id": self.space_id, "principal_type": self.principal_type, "principal_id": self.principal_id, "access_level": self.access_level.value, "role": self.role.value if self.role else None, "granted_by": self.granted_by, "granted_at": self.granted_at.isoformat(), "expires_at": self.expires_at.isoformat() if self.expires_at else None, } @classmethod def from_dict(cls, data: Dict[str, Any]) -> "SpacePermission": """Create from dictionary.""" granted_at = data.get("granted_at") if isinstance(granted_at, str): granted_at = datetime.fromisoformat(granted_at) elif granted_at is None: granted_at = datetime.now() expires_at = data.get("expires_at") if isinstance(expires_at, str): expires_at = datetime.fromisoformat(expires_at) access_level = data.get("access_level", "read") if isinstance(access_level, str): access_level = AccessLevel(access_level) role = data.get("role") if isinstance(role, str): role = SpaceRole(role) return cls( space_id=data["space_id"], principal_type=data.get("principal_type", "user"), principal_id=data["principal_id"], access_level=access_level, role=role, granted_by=data.get("granted_by"), granted_at=granted_at, expires_at=expires_at, ) def is_expired(self) -> bool: """Check if permission has expired.""" if self.expires_at is None: return False return datetime.now() > self.expires_at def has_access(self, required_level: AccessLevel) -> bool: """Check if this permission grants the required access level.""" if self.is_expired(): return False level_order = [AccessLevel.NONE, AccessLevel.READ, AccessLevel.WRITE, AccessLevel.ADMIN] return level_order.index(self.access_level) >= level_order.index(required_level) @dataclass class SpaceAccess: """ Computed access result for a principal on a space. Combines all applicable permissions to determine effective access. Attributes: space_id: The space being accessed principal_id: The principal accessing effective_level: Computed effective access level roles: All roles that apply inherited_from: Space IDs from which access was inherited permissions: Source permissions that contributed """ space_id: str principal_id: str effective_level: AccessLevel = AccessLevel.NONE roles: List[SpaceRole] = field(default_factory=list) inherited_from: List[str] = field(default_factory=list) permissions: List[SpacePermission] = field(default_factory=list) def can_read(self) -> bool: """Check if principal can read.""" return self.effective_level in [AccessLevel.READ, AccessLevel.WRITE, AccessLevel.ADMIN] def can_write(self) -> bool: """Check if principal can write.""" return self.effective_level in [AccessLevel.WRITE, AccessLevel.ADMIN] def is_admin(self) -> bool: """Check if principal has admin access.""" return self.effective_level == AccessLevel.ADMIN @dataclass class InheritedVariable: """ A variable with its inheritance source. Attributes: name: Variable name value: Variable value source_space_id: Space where this variable is defined inheritance_depth: How many levels up the inheritance chain (0 = local) scope: Variable scope """ name: str value: Any source_space_id: str inheritance_depth: int = 0 scope: str = "space" def is_local(self) -> bool: """Check if variable is locally defined (not inherited).""" return self.inheritance_depth == 0 @dataclass class InheritedConfig: """ Configuration with inheritance tracking. Attributes: default_variant: Effective default variant enable_caching: Effective caching setting theme: Effective theme history_enabled: Effective history setting variable_scope: Effective variable scope source_spaces: Map of config key to source space ID """ default_variant: str = "hierarchical" enable_caching: bool = True theme: Optional[str] = None history_enabled: bool = False variable_scope: str = "space" source_spaces: Dict[str, str] = field(default_factory=dict) def get_source(self, key: str) -> Optional[str]: """Get the source space ID for a config key.""" return self.source_spaces.get(key) def is_inherited(self, key: str, current_space_id: str) -> bool: """Check if a config key is inherited from a parent.""" source = self.source_spaces.get(key) return source is not None and source != current_space_id