PYTHONPython

models

real world projects / password manager / pwm

PYTHON
models.py🐍
"""
Data models for password entries.
"""

from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Optional, List
import json
import uuid


def generate_id() -> str:
    """Generate unique ID."""
    return str(uuid.uuid4())


@dataclass
class PasswordEntry:
    """Represents a single password entry."""
    
    name: str
    username: str = ""
    password: str = ""
    url: str = ""
    notes: str = ""
    category: str = "General"
    tags: Optional[List[str]] = None
    id: str = field(default_factory=generate_id)
    created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    
    def __post_init__(self):
        """Initialize mutable defaults."""
        if self.tags is None:
            self.tags = []
    
    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return asdict(self)
    
    @classmethod
    def from_dict(cls, data: dict) -> "PasswordEntry":
        """Create from dictionary."""
        return cls(**data)
    
    def update(self, **kwargs):
        """Update entry fields."""
        for key, value in kwargs.items():
            if hasattr(self, key):
                setattr(self, key, value)
        self.updated_at = datetime.utcnow().isoformat()
    
    def __str__(self) -> str:
        return f"PasswordEntry(name={self.name}, username={self.username})"


@dataclass
class Vault:
    """Represents the password vault."""
    
    version: str = "1.0"
    entries: List[PasswordEntry] = field(default_factory=list)
    created_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    updated_at: str = field(default_factory=lambda: datetime.utcnow().isoformat())
    
    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "version": self.version,
            "entries": [e.to_dict() for e in self.entries],
            "created_at": self.created_at,
            "updated_at": self.updated_at
        }
    
    @classmethod
    def from_dict(cls, data: dict) -> "Vault":
        """Create from dictionary."""
        entries = [PasswordEntry.from_dict(e) for e in data.get("entries", [])]
        return cls(
            version=data.get("version", "1.0"),
            entries=entries,
            created_at=data.get("created_at", ""),
            updated_at=data.get("updated_at", "")
        )
    
    def to_json(self) -> str:
        """Serialize to JSON."""
        return json.dumps(self.to_dict(), indent=2)
    
    @classmethod
    def from_json(cls, json_str: str) -> "Vault":
        """Deserialize from JSON."""
        data = json.loads(json_str)
        return cls.from_dict(data)
    
    def add(self, entry: PasswordEntry):
        """Add an entry."""
        # Check for duplicates
        for existing in self.entries:
            if existing.name.lower() == entry.name.lower():
                raise ValueError(f"Entry '{entry.name}' already exists")
        
        self.entries.append(entry)
        self.updated_at = datetime.utcnow().isoformat()
    
    def get(self, name: str) -> Optional[PasswordEntry]:
        """Get an entry by name."""
        for entry in self.entries:
            if entry.name.lower() == name.lower():
                return entry
        return None
    
    def update(self, name: str, **kwargs) -> bool:
        """Update an entry."""
        entry = self.get(name)
        if entry:
            entry.update(**kwargs)
            self.updated_at = datetime.utcnow().isoformat()
            return True
        return False
    
    def delete(self, name: str) -> bool:
        """Delete an entry."""
        for i, entry in enumerate(self.entries):
            if entry.name.lower() == name.lower():
                del self.entries[i]
                self.updated_at = datetime.utcnow().isoformat()
                return True
        return False
    
    def search(
        self,
        query: str = "",
        category: str = "",
        tags: Optional[List[str]] = None
    ) -> List[PasswordEntry]:
        """Search entries."""
        results = []
        
        for entry in self.entries:
            # Text search
            if query:
                query_lower = query.lower()
                if not any([
                    query_lower in entry.name.lower(),
                    query_lower in entry.username.lower(),
                    query_lower in entry.url.lower(),
                    query_lower in entry.notes.lower()
                ]):
                    continue
            
            # Category filter
            if category and entry.category.lower() != category.lower():
                continue
            
            # Tags filter
            if tags:
                entry_tags = [t.lower() for t in (entry.tags or [])]
                if not any(t.lower() in entry_tags for t in tags):
                    continue
            
            results.append(entry)
        
        return results
    
    def list_categories(self) -> List[str]:
        """Get unique categories."""
        return list(set(e.category for e in self.entries))
    
    def list_tags(self) -> List[str]:
        """Get unique tags."""
        all_tags: set[str] = set()
        for entry in self.entries:
            if entry.tags:
                all_tags.update(entry.tags)
        return list(all_tags)
PreviousNext