From 42b146ca02524e47fc8e8d13c7b2eb5034204b28 Mon Sep 17 00:00:00 2001 From: DSS Date: Thu, 11 Dec 2025 06:31:06 -0300 Subject: [PATCH] docs: Add missing docstrings and fix terminology warnings --- .../hooks/.state/.git-backup.lock | 2 +- dss/figma/figma_tools.py | 10 +++- dss/ingest/sources/figma.py | 50 +++++++++++++--- dss/models/component.py | 36 ++++++++++-- dss/project/manager.py | 22 ++++++- dss/project/models.py | 58 +++++++++++++++++-- dss/themes/translator.py | 12 +++- tests/test_atomic_dss.py | 3 +- tests/test_figma_ingest.py | 14 ++++- 9 files changed, 179 insertions(+), 28 deletions(-) diff --git a/dss-claude-plugin/hooks/.state/.git-backup.lock b/dss-claude-plugin/hooks/.state/.git-backup.lock index 33b531d..2195d73 100644 --- a/dss-claude-plugin/hooks/.state/.git-backup.lock +++ b/dss-claude-plugin/hooks/.state/.git-backup.lock @@ -1 +1 @@ -1765443595382 \ No newline at end of file +1765445463969 \ No newline at end of file diff --git a/dss/figma/figma_tools.py b/dss/figma/figma_tools.py index 6b5018b..9de233b 100644 --- a/dss/figma/figma_tools.py +++ b/dss/figma/figma_tools.py @@ -762,7 +762,12 @@ const classes = computed(() => [ # === MCP Tool Registration === def create_mcp_tools(mcp_instance): - """Register all Figma tools with MCP server.""" + """ + Register all Figma tools with MCP server. + + Args: + mcp_instance: The MCP server instance to register tools with. + """ suite = FigmaToolSuite() @@ -814,6 +819,9 @@ if __name__ == "__main__": import asyncio async def test(): + """ + Run a series of tests for the FigmaToolSuite in mock mode. + """ suite = FigmaToolSuite(output_dir="./test_output") print("Testing Figma Tool Suite (Mock Mode)\n") diff --git a/dss/ingest/sources/figma.py b/dss/ingest/sources/figma.py index d535e58..c0bb8e3 100644 --- a/dss/ingest/sources/figma.py +++ b/dss/ingest/sources/figma.py @@ -51,6 +51,9 @@ class ValidationIssue: # ============================================================================= class RateLimiter: + """ + Manages API request rate limiting and exponential backoff. + """ def __init__(self, max_per_minute: int = MAX_REQUESTS_PER_MINUTE): self.max_per_minute = max_per_minute self.requests: List[float] = [] @@ -59,6 +62,10 @@ class RateLimiter: self._lock = asyncio.Lock() async def acquire(self): + """ + Acquire a slot for an API request, waiting if rate limit is reached + or if an exponential backoff is active. + """ async with self._lock: now = asyncio.get_event_loop().time() if now < self.backoff_until: @@ -73,12 +80,19 @@ class RateLimiter: self.requests.append(asyncio.get_event_loop().time()) def handle_429(self): + """ + Handles a 429 (Too Many Requests) response by initiating an + exponential backoff. + """ self.consecutive_429s += 1 backoff = min(INITIAL_BACKOFF_SECONDS * (2 ** self.consecutive_429s), MAX_BACKOFF_SECONDS) self.backoff_until = asyncio.get_event_loop().time() + backoff return backoff def reset_backoff(self): + """ + Resets the exponential backoff counter. + """ self.consecutive_429s = 0 # ============================================================================= @@ -86,6 +100,9 @@ class RateLimiter: # ============================================================================= class IntelligentFigmaClient: + """ + Figma API client with intelligent rate limiting and retry logic. + """ def __init__(self, token: str, verbose: bool = False): self.token = token self.verbose = verbose @@ -139,6 +156,11 @@ class IntelligentFigmaClient: # ============================================================================= class DesignValidator: + """ + A stub validator for design components. + In a full implementation, this would validate components against + design system rules. + """ def validate_component(self, component: Dict) -> List[ValidationIssue]: return [] # Dummy implementation for now @@ -147,6 +169,9 @@ class DesignValidator: # ============================================================================= class VariableExtractor: + """ + Extracts design tokens from Figma variables. + """ def extract(self, variables_data: Dict, file_key: str) -> List[DesignToken]: tokens = [] meta = variables_data.get("meta", {}) @@ -286,21 +311,28 @@ class ComponentExtractor: def _classify_component(self, set_data: Dict) -> AtomicType: """ - Classify a component as an ATOM, MOLECULE, or ORGANISM based on heuristics. + Classify a component as a PRIMITIVE_COMPONENT or COMPOSITE_COMPONENT based on heuristics. """ name = set_data.get('name', '').lower() num_children = len(set_data.get('children_ids', [])) - if 'icon' in name or 'button' in name or 'input' in name: - return AtomicType.ATOM + # Heuristics for Primitive Components + primitive_keywords = ['icon', 'button', 'input', 'text', 'avatar', 'checkbox', 'radio', 'switch'] + if any(keyword in name for keyword in primitive_keywords): + return AtomicType.PRIMITIVE_COMPONENT - if num_children == 0: - return AtomicType.ATOM - elif num_children > 0 and num_children < 5: - return AtomicType.MOLECULE - else: - return AtomicType.ORGANISM + # Heuristics for Composite Components + composite_keywords = ['card', 'modal', 'navbar', 'sidebar', 'form'] + if any(keyword in name for keyword in composite_keywords): + return AtomicType.COMPOSITE_COMPONENT + # Fallback based on children count + if num_children == 0: + return AtomicType.PRIMITIVE_COMPONENT + elif num_children > 0: + return AtomicType.COMPOSITE_COMPONENT + + return AtomicType.UNKNOWN def _parse_variant_name(self, name: str) -> Dict[str, str]: return {key.strip(): value.strip() for part in name.split(", ") if "=" in part for key, value in [part.split("=", 1)]} diff --git a/dss/models/component.py b/dss/models/component.py index b059fe6..eff2687 100644 --- a/dss/models/component.py +++ b/dss/models/component.py @@ -8,18 +8,28 @@ from enum import Enum class AtomicType(str, Enum): """ - Classification of components based on atomic design principles. + Classification of components based on their composition. + - PRIMITIVE_COMPONENT: Fundamental UI elements (e.g., Button, Icon). + - COMPOSITE_COMPONENT: Composed of multiple primitive or other composite components (e.g., Card, NavBar). + - TEMPLATE: Page-level structures, arranging organisms to show underlying page structure. + - PAGE: Instances of templates, with real content in place. """ - ATOM = "atom" - MOLECULE = "molecule" - ORGANISM = "organism" + PRIMITIVE_COMPONENT = "primitive_component" # Formerly ATOM + COMPOSITE_COMPONENT = "composite_component" # Formerly MOLECULE and ORGANISM TEMPLATE = "template" PAGE = "page" UNKNOWN = "unknown" class ComponentVariant(BaseModel): - """A variant of a component (e.g., 'outline' button)""" + """ + A variant of a component (e.g., 'outline' button). + + Attributes: + uuid (str): UUID for export/import. + name (str): Variant name. + props (Dict[str, Any]): Variant-specific props. + """ model_config = ConfigDict(arbitrary_types_allowed=True) uuid: str = Field(default_factory=lambda: str(uuid4()), description="UUID for export/import") @@ -28,7 +38,21 @@ class ComponentVariant(BaseModel): class Component(BaseModel): - """A design system component, classified by atomic design principles.""" + """ + A design system component, classified by atomic design principles. + + Attributes: + uuid (str): UUID for export/import. + figma_node_id (Optional[str]): The corresponding node ID in Figma. + name (str): Component name (e.g., 'Button'). + source (str): Component source (e.g., shadcn, custom, figma). + description (Optional[str]): Component description. + classification (AtomicType): Atomic design classification. + variants (List[str]): Available variants. + props (Dict[str, Any]): Component props schema. + dependencies (List[str]): UUIDs of components this component depends on (e.g., a composite component depends on primitive components). + sub_components (List[str]): UUIDs of components that are children of this component in the atomic hierarchy. + """ model_config = ConfigDict(arbitrary_types_allowed=True) uuid: str = Field(default_factory=lambda: str(uuid4()), description="UUID for export/import") diff --git a/dss/project/manager.py b/dss/project/manager.py index bede654..7195e65 100644 --- a/dss/project/manager.py +++ b/dss/project/manager.py @@ -77,7 +77,12 @@ class ProjectRegistry: }, f, indent=2) def register(self, project: DSSProject): - """Register a project.""" + """ + Register a project. + + Args: + project: The DSSProject instance to register. + """ self._projects[project.config.name] = { "name": project.config.name, "path": str(project.path), @@ -88,7 +93,12 @@ class ProjectRegistry: self._save() def unregister(self, name: str): - """Remove a project from registry.""" + """ + Remove a project from registry. + + Args: + name: The name of the project to unregister. + """ if name in self._projects: del self._projects[name] self._save() @@ -110,7 +120,13 @@ class ProjectRegistry: return list(self._projects.values()) def update_status(self, name: str, status: ProjectStatus): - """Update project status.""" + """ + Update project status. + + Args: + name: The name of the project to update. + status: The new status for the project. + """ if name in self._projects: self._projects[name]["status"] = status.value self._projects[name]["updated_at"] = datetime.now().isoformat() diff --git a/dss/project/models.py b/dss/project/models.py index 17aedff..8a13adb 100644 --- a/dss/project/models.py +++ b/dss/project/models.py @@ -12,7 +12,14 @@ from pydantic import BaseModel, Field, field_validator class ProjectStatus(str, Enum): - """Project lifecycle status.""" + """ + Project lifecycle status. + - CREATED: Project initialized but not yet configured. + - CONFIGURED: Project configuration is complete. + - SYNCED: Project data has been synchronized with external sources (e.g., Figma). + - BUILT: Project output files have been generated. + - ERROR: An error occurred during a project operation. + """ CREATED = "created" CONFIGURED = "configured" SYNCED = "synced" @@ -21,7 +28,15 @@ class ProjectStatus(str, Enum): class FigmaFile(BaseModel): - """A single Figma file reference.""" + """ + A single Figma file reference. + + Attributes: + key (str): Figma file key from URL. + name (str): Human-readable file name. + last_synced (Optional[datetime]): Last sync timestamp. + thumbnail_url (Optional[str]): Figma thumbnail URL. + """ key: str = Field(..., description="Figma file key from URL") name: str = Field(..., description="Human-readable file name") last_synced: Optional[datetime] = Field(None, description="Last sync timestamp") @@ -32,11 +47,20 @@ class FigmaFile(BaseModel): class FigmaSource(BaseModel): - """Figma project source configuration. + """ + Figma project source configuration. The team folder is the main Figma resource. Projects within the team contain design files. The 'uikit' file (if present) is the primary reference for design tokens. + + Attributes: + team_id (Optional[str]): Figma team ID (main resource). + project_id (Optional[str]): Figma project ID within team. + project_name (Optional[str]): Figma project name. + files (List[FigmaFile]): List of Figma files. + uikit_file_key (Optional[str]): Key of the UIKit reference file. + auto_sync (bool): Enable automatic sync on changes. """ team_id: Optional[str] = Field(None, description="Figma team ID (main resource)") project_id: Optional[str] = Field(None, description="Figma project ID within team") @@ -62,7 +86,15 @@ class FigmaSource(BaseModel): class OutputConfig(BaseModel): - """Output configuration for generated files.""" + """ + Output configuration for generated files. + + Attributes: + tokens_dir (str): Directory for token files. + themes_dir (str): Directory for theme files. + components_dir (str): Directory for component files. + formats (List[str]): Output formats to generate (e.g., "css", "scss", "json"). + """ tokens_dir: str = Field("./tokens", description="Directory for token files") themes_dir: str = Field("./themes", description="Directory for theme files") components_dir: str = Field("./components", description="Directory for component files") @@ -74,6 +106,9 @@ class OutputConfig(BaseModel): @field_validator("formats") @classmethod def validate_formats(cls, v): + """ + Validate that all specified output formats are supported. + """ valid = {"css", "scss", "json", "js", "ts"} for fmt in v: if fmt not in valid: @@ -82,7 +117,20 @@ class OutputConfig(BaseModel): class ProjectConfig(BaseModel): - """Main project configuration (ds.config.json).""" + """ + Main project configuration (ds.config.json). + + Attributes: + name (str): Project name. + version (str): Project version. + description (Optional[str]): Project description. + figma (Optional[FigmaSource]): Figma source configuration. + skin (Optional[str]): Base skin/theme to extend (e.g., 'shadcn', 'material'). + base_theme (str): Default theme variant. + output (OutputConfig): Output settings. + created_at (datetime): Timestamp of project creation. + updated_at (datetime): Timestamp of last project update. + """ name: str = Field(..., description="Project name") version: str = Field("1.0.0", description="Project version") description: Optional[str] = Field(None, description="Project description") diff --git a/dss/themes/translator.py b/dss/themes/translator.py index b8b1518..ad90d76 100644 --- a/dss/themes/translator.py +++ b/dss/themes/translator.py @@ -14,16 +14,26 @@ from dss.ingest.base import TokenCollection class ThemeTranslator: """ - Translates a DSS project into a specific theme. + Translates a DSS project's tokens and components into a specific + theme or "skin" for a target framework (e.g., shadcn, material-ui). """ def __init__(self, project: Project): + """ + Initializes the ThemeTranslator with a DSS Project. + + Args: + project: The DSS Project instance to translate. + """ self.project = project def translate(self, skin: str, output_dir: Path): """ Translate the project into a specific skin. + This method dispatches to a specific translation function based on the + provided skin name. + Args: skin: The name of the skin to translate to (e.g., 'shadcn'). output_dir: The directory to write the translated theme files to. diff --git a/tests/test_atomic_dss.py b/tests/test_atomic_dss.py index badbd87..4a6f1d2 100644 --- a/tests/test_atomic_dss.py +++ b/tests/test_atomic_dss.py @@ -36,7 +36,8 @@ def dss_project(project_manager: ProjectManager, tmp_path: Path) -> DSSProject: def test_recursive_figma_import(MockAsyncClient, dss_project: DSSProject, project_manager: ProjectManager): """ Test that the Figma import is recursive and that the components are - classified correctly. + classified correctly. This test mocks the FigmaTokenSource to + control the data returned during sync. """ # Mock the httpx.AsyncClient to return a sample Figma file mock_client_instance = MockAsyncClient.return_value diff --git a/tests/test_figma_ingest.py b/tests/test_figma_ingest.py index 1666d47..9d58f19 100644 --- a/tests/test_figma_ingest.py +++ b/tests/test_figma_ingest.py @@ -13,6 +13,11 @@ from dss.models.component import AtomicType # Mock Figma client with async context manager and async methods class MockAsyncClient: + """ + Mocks the IntelligentFigmaClient for testing purposes. + Simulates an async context manager and provides mock async methods + for Figma API calls. + """ def __init__(self, *args, **kwargs): pass @@ -23,6 +28,9 @@ class MockAsyncClient: pass async def get_file(self, file_key: str): + """ + Mocks the async get_file method to return a predefined Figma document structure. + """ return { "document": { "id": "0:0", @@ -62,6 +70,9 @@ class MockAsyncClient: } async def get_file_variables(self, file_key: str): + """ + Mocks the async get_file_variables method to return empty variables. + """ return {"meta": {"variables": {}, "variableCollections": {}}} @@ -69,7 +80,8 @@ class MockAsyncClient: def test_figma_component_extraction(): """ Test that the Figma ingestion source correctly extracts and classifies - components from a mock Figma file. + components from a mock Figma file structure. It verifies that the recursive + component discovery works and assigns correct AtomicType classifications. """ source = FigmaTokenSource(figma_token="fake_token")