# models.py # # Copyright 2025 Pavel Baksy # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # SPDX-License-Identifier: GPL-3.0-or-later from dataclasses import dataclass, asdict from typing import Dict, Optional, List @dataclass class HttpRequest: """Represents an HTTP request.""" method: str # "GET", "POST", "PUT", "DELETE" url: str headers: Dict[str, str] # Key-value pairs body: str # Raw text body syntax: str = "RAW" # Syntax highlighting: "RAW", "JSON", or "XML" def to_dict(self): """Convert to dictionary for JSON serialization.""" return asdict(self) @classmethod def from_dict(cls, data): """Create instance from dictionary.""" # Provide default for syntax field for backward compatibility if 'syntax' not in data: data = {**data, 'syntax': 'RAW'} return cls(**data) @dataclass class HttpResponse: """Represents an HTTP response.""" status_code: int status_text: str # e.g., "OK" headers: str # Raw header text from libsoup3 body: str # Raw body text response_time_ms: float def to_dict(self): """Convert to dictionary for JSON serialization.""" return asdict(self) @classmethod def from_dict(cls, data): """Create instance from dictionary.""" return cls(**data) @dataclass class HistoryEntry: """Represents a request/response pair in history.""" timestamp: str # ISO format datetime request: HttpRequest response: Optional[HttpResponse] error: Optional[str] # Error message if request failed id: str = None # Unique identifier for the entry def __post_init__(self): """Generate UUID if id not provided.""" if self.id is None: import uuid self.id = str(uuid.uuid4()) def to_dict(self): """Convert to dictionary for JSON serialization.""" return { 'id': self.id, 'timestamp': self.timestamp, 'request': self.request.to_dict(), 'response': self.response.to_dict() if self.response else None, 'error': self.error } @classmethod def from_dict(cls, data): """Create instance from dictionary.""" return cls( id=data.get('id'), # Backwards compatible - will generate if missing timestamp=data['timestamp'], request=HttpRequest.from_dict(data['request']), response=HttpResponse.from_dict(data['response']) if data.get('response') else None, error=data.get('error') ) @dataclass class SavedRequest: """A saved HTTP request with metadata.""" id: str # UUID name: str # User-provided name request: HttpRequest created_at: str # ISO format modified_at: str # ISO format scripts: Optional['Scripts'] = None # Scripts for preprocessing and postprocessing def to_dict(self): """Convert to dictionary for JSON serialization.""" return { 'id': self.id, 'name': self.name, 'request': self.request.to_dict(), 'created_at': self.created_at, 'modified_at': self.modified_at, 'scripts': self.scripts.to_dict() if self.scripts else None } @classmethod def from_dict(cls, data): """Create instance from dictionary.""" return cls( id=data['id'], name=data['name'], request=HttpRequest.from_dict(data['request']), created_at=data['created_at'], modified_at=data['modified_at'], scripts=Scripts.from_dict(data['scripts']) if data.get('scripts') else None ) @dataclass class Environment: """An environment with variable values.""" id: str # UUID name: str # e.g., "production", "integration", "stage" variables: Dict[str, str] # variable_name -> value created_at: str # ISO format def to_dict(self): """Convert to dictionary for JSON serialization.""" return { 'id': self.id, 'name': self.name, 'variables': self.variables, 'created_at': self.created_at } @classmethod def from_dict(cls, data): """Create instance from dictionary.""" return cls( id=data['id'], name=data['name'], variables=data.get('variables', {}), created_at=data['created_at'] ) @dataclass class Project: """A project containing saved requests and environments.""" id: str # UUID name: str requests: List[SavedRequest] created_at: str # ISO format icon: str = "folder-symbolic" # Icon name variable_names: List[str] = None # List of variable names defined for this project environments: List[Environment] = None # List of environments def __post_init__(self): """Initialize optional fields.""" if self.variable_names is None: self.variable_names = [] if self.environments is None: self.environments = [] def to_dict(self): """Convert to dictionary for JSON serialization.""" return { 'id': self.id, 'name': self.name, 'requests': [req.to_dict() for req in self.requests], 'created_at': self.created_at, 'icon': self.icon, 'variable_names': self.variable_names, 'environments': [env.to_dict() for env in self.environments] } @classmethod def from_dict(cls, data): """Create instance from dictionary.""" return cls( id=data['id'], name=data['name'], requests=[SavedRequest.from_dict(r) for r in data.get('requests', [])], created_at=data['created_at'], icon=data.get('icon', 'folder-symbolic'), # Default for old data variable_names=data.get('variable_names', []), environments=[Environment.from_dict(e) for e in data.get('environments', [])] ) @dataclass class RequestTab: """Represents an open request tab in the UI.""" id: str # UUID for tab identification name: str # Display name (from SavedRequest or URL) request: HttpRequest # Current request state response: Optional[HttpResponse] = None # Last response (if sent) saved_request_id: Optional[str] = None # ID if from SavedRequest modified: bool = False # True if has unsaved changes original_request: Optional[HttpRequest] = None # For change detection project_id: Optional[str] = None # Project association for environment variables selected_environment_id: Optional[str] = None # Selected environment for variable substitution scripts: Optional['Scripts'] = None # Scripts for preprocessing and postprocessing def is_modified(self) -> bool: """Check if current request differs from original.""" if not self.original_request: # New unsaved request - consider modified if has content return bool(self.request.url or self.request.body or self.request.headers) return ( self.request.method != self.original_request.method or self.request.url != self.original_request.url or self.request.headers != self.original_request.headers or self.request.body != self.original_request.body or self.request.syntax != self.original_request.syntax ) def to_dict(self): """Convert to dictionary for JSON serialization (for session persistence).""" return { 'id': self.id, 'name': self.name, 'request': self.request.to_dict(), 'response': self.response.to_dict() if self.response else None, 'saved_request_id': self.saved_request_id, 'modified': self.modified, 'original_request': self.original_request.to_dict() if self.original_request else None, 'project_id': self.project_id, 'selected_environment_id': self.selected_environment_id, 'scripts': self.scripts.to_dict() if self.scripts else None } @classmethod def from_dict(cls, data): """Create instance from dictionary (for session restoration).""" return cls( id=data['id'], name=data['name'], request=HttpRequest.from_dict(data['request']), response=HttpResponse.from_dict(data['response']) if data.get('response') else None, saved_request_id=data.get('saved_request_id'), modified=data.get('modified', False), original_request=HttpRequest.from_dict(data['original_request']) if data.get('original_request') else None, project_id=data.get('project_id'), selected_environment_id=data.get('selected_environment_id'), scripts=Scripts.from_dict(data['scripts']) if data.get('scripts') else None ) @dataclass class Scripts: """Scripts for request preprocessing and postprocessing.""" preprocessing: str = "" postprocessing: str = "" def to_dict(self): """Convert to dictionary for JSON serialization.""" return { 'preprocessing': self.preprocessing, 'postprocessing': self.postprocessing } @classmethod def from_dict(cls, data): """Create instance from dictionary.""" return cls( preprocessing=data.get('preprocessing', ''), postprocessing=data.get('postprocessing', '') )