PYTHON
models.py🐍python
"""
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)