roster/src/models.py

279 lines
9.6 KiB
Python

# 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 <https://www.gnu.org/licenses/>.
#
# 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
def to_dict(self):
"""Convert to dictionary for JSON serialization."""
return {
'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(
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', '')
)