279 lines
9.6 KiB
Python
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', '')
|
|
)
|