"""Core documentation object representing a Python entity. DocObject is the atomic unit of documentation in doc-forge. It represents any Python entity that can be documented: classes, functions, methods, attributes, constants, etc. Each DocObject contains: - Basic metadata (name, kind, path) - Optional signature information - Optional docstring - Nested members (for classes and modules) """ from __future__ import annotations from typing import Dict, Optional, Set class DocObject: """Represents a Python documentation object (class, function, variable, etc.). DocObject is the fundamental building block of the documentation model. It captures all essential information about a Python entity in a renderer-agnostic format. Attributes: name: The name of the object (e.g., "MyClass", "my_function") kind: The type of object ("class", "function", "method", "attribute", etc.) path: The full import path (e.g., "package.module.MyClass.my_method") signature: Optional function/method signature string docstring: Optional docstring content members: Dictionary of nested member objects """ def __init__( self, name: str, kind: str, path: str, signature: Optional[str] = None, docstring: Optional[str] = None, ) -> None: """Initialize a DocObject. Args: name: The name of the object kind: The type/kind of object path: Full import path to the object signature: Optional signature for callable objects docstring: Optional docstring content Raises: ValueError: If name, kind, or path are empty """ if not name: raise ValueError("DocObject name cannot be empty") if not kind: raise ValueError("DocObject kind cannot be empty") if not path: raise ValueError("DocObject path cannot be empty") self.name: str = name self.kind: str = kind self.path: str = path self.signature: Optional[str] = signature self.docstring: Optional[str] = docstring self.members: Dict[str, DocObject] = {} def add_member(self, member: DocObject) -> None: """Add a nested member object. This is used for objects that contain other objects, such as classes containing methods and attributes, or modules containing functions and classes. Args: member: The member object to add Raises: ValueError: If member name conflicts with existing member """ if member.name in self.members: raise ValueError(f"Member '{member.name}' already exists in '{self.name}'") self.members[member.name] = member def get_member(self, name: str) -> Optional[DocObject]: """Get a nested member by name. Args: name: The name of the member to retrieve Returns: The member object if found, None otherwise """ return self.members.get(name) def is_private(self) -> bool: """Check if this object is considered private. Private objects are those whose names start with an underscore. This convention is used to filter out internal implementation details from public documentation. Returns: True if the object name starts with underscore, False otherwise """ return self.name.startswith('_') def get_public_members(self) -> list[DocObject]: """Get all public (non-private) member objects. Returns: List of member objects that are not private """ return [member for member in self.members.values() if not member.is_private()] def has_docstring(self) -> bool: """Check if this object has a docstring. Returns: True if docstring is not None and not empty, False otherwise """ return bool(self.docstring and self.docstring.strip()) def __repr__(self) -> str: """Return a string representation of the DocObject. Returns: String representation showing name, kind, and path """ return f"DocObject(name='{self.name}', kind='{self.kind}', path='{self.path}')"