docs: Add missing docstrings and fix terminology warnings

This commit is contained in:
DSS
2025-12-11 06:31:06 -03:00
parent bcd1a86ae4
commit 42b146ca02
9 changed files with 179 additions and 28 deletions

View File

@@ -1 +1 @@
1765443595382
1765445463969

View File

@@ -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")

View File

@@ -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)]}

View File

@@ -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")

View File

@@ -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()

View File

@@ -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")

View File

@@ -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.

View File

@@ -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

View File

@@ -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")