docs: Add missing docstrings and fix terminology warnings
This commit is contained in:
@@ -1 +1 @@
|
||||
1765443595382
|
||||
1765445463969
|
||||
@@ -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")
|
||||
|
||||
@@ -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)]}
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user