Files
dss/dss/project/models.py
Bruno Sarlo 41fba59bf7 Major refactor: Consolidate DSS into unified package structure
- Create new dss/ Python package at project root
- Move MCP core from tools/dss_mcp/ to dss/mcp/
- Move storage layer from tools/storage/ to dss/storage/
- Move domain logic from dss-mvp1/dss/ to dss/
- Move services from tools/api/services/ to dss/services/
- Move API server to apps/api/
- Move CLI to apps/cli/
- Move Storybook assets to storybook/
- Create unified dss/__init__.py with comprehensive exports
- Merge configuration into dss/settings.py (Pydantic-based)
- Create pyproject.toml for proper package management
- Update startup scripts for new paths
- Remove old tools/ and dss-mvp1/ directories

Architecture changes:
- DSS is now MCP-first with 40+ tools for Claude Code
- Clean imports: from dss import Projects, Components, FigmaToolSuite
- No more sys.path.insert() hacking
- apps/ contains thin application wrappers (API, CLI)
- Single unified Python package for all DSS logic

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 12:46:43 -03:00

170 lines
5.9 KiB
Python

"""
DSS Project Models
Pydantic models for project configuration and state.
"""
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel, Field, field_validator
class ProjectStatus(str, Enum):
"""Project lifecycle status."""
CREATED = "created"
CONFIGURED = "configured"
SYNCED = "synced"
BUILT = "built"
ERROR = "error"
class FigmaFile(BaseModel):
"""A single Figma file reference."""
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")
thumbnail_url: Optional[str] = Field(None, description="Figma thumbnail URL")
class Config:
json_encoders = {datetime: lambda v: v.isoformat() if v else None}
class FigmaSource(BaseModel):
"""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.
"""
team_id: Optional[str] = Field(None, description="Figma team ID (main resource)")
project_id: Optional[str] = Field(None, description="Figma project ID within team")
project_name: Optional[str] = Field(None, description="Figma project name")
files: List[FigmaFile] = Field(default_factory=list, description="List of Figma files")
uikit_file_key: Optional[str] = Field(None, description="Key of the UIKit reference file")
auto_sync: bool = Field(False, description="Enable automatic sync on changes")
def add_file(self, key: str, name: str, thumbnail_url: Optional[str] = None) -> FigmaFile:
"""Add a file to the source."""
file = FigmaFile(key=key, name=name, thumbnail_url=thumbnail_url)
# Check for duplicates
if not any(f.key == key for f in self.files):
self.files.append(file)
return file
def get_file(self, key: str) -> Optional[FigmaFile]:
"""Get a file by key."""
for f in self.files:
if f.key == key:
return f
return None
class OutputConfig(BaseModel):
"""Output configuration for generated files."""
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")
formats: List[str] = Field(
default_factory=lambda: ["css", "scss", "json"],
description="Output formats to generate"
)
@field_validator("formats")
@classmethod
def validate_formats(cls, v):
valid = {"css", "scss", "json", "js", "ts"}
for fmt in v:
if fmt not in valid:
raise ValueError(f"Invalid format: {fmt}. Must be one of {valid}")
return v
class ProjectConfig(BaseModel):
"""Main project configuration (ds.config.json)."""
name: str = Field(..., description="Project name")
version: str = Field("1.0.0", description="Project version")
description: Optional[str] = Field(None, description="Project description")
# Sources
figma: Optional[FigmaSource] = Field(None, description="Figma source configuration")
# Design system settings
skin: Optional[str] = Field(None, description="Base skin/theme to extend (e.g., 'shadcn', 'material')")
base_theme: str = Field("light", description="Default theme variant")
# Output configuration
output: OutputConfig = Field(default_factory=OutputConfig, description="Output settings")
# Metadata
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
class Config:
json_encoders = {datetime: lambda v: v.isoformat() if v else None}
class DSSProject(BaseModel):
"""
Complete DSS Project representation.
Combines configuration with runtime state.
"""
config: ProjectConfig = Field(..., description="Project configuration")
path: Path = Field(..., description="Absolute path to project directory")
status: ProjectStatus = Field(ProjectStatus.CREATED, description="Current project status")
# Runtime state
errors: List[str] = Field(default_factory=list, description="Error messages")
warnings: List[str] = Field(default_factory=list, description="Warning messages")
# Extracted data (populated after sync)
extracted_tokens: Optional[Dict[str, Any]] = Field(None, description="Tokens from sources")
class Config:
arbitrary_types_allowed = True
json_encoders = {
datetime: lambda v: v.isoformat() if v else None,
Path: str,
}
@property
def config_path(self) -> Path:
"""Path to ds.config.json."""
return self.path / "ds.config.json"
@property
def tokens_path(self) -> Path:
"""Path to tokens directory."""
return self.path / self.config.output.tokens_dir
@property
def themes_path(self) -> Path:
"""Path to themes directory."""
return self.path / self.config.output.themes_dir
def to_config_dict(self) -> Dict[str, Any]:
"""Export configuration for saving to ds.config.json."""
return self.config.model_dump(mode="json", exclude_none=True)
@classmethod
def from_config_file(cls, config_path: Path) -> "DSSProject":
"""Load project from ds.config.json file."""
import json
if not config_path.exists():
raise FileNotFoundError(f"Config file not found: {config_path}")
with open(config_path, "r") as f:
config_data = json.load(f)
config = ProjectConfig(**config_data)
project_path = config_path.parent
return cls(
config=config,
path=project_path,
status=ProjectStatus.CONFIGURED,
)