Lint cleanup: ruff fixes across entire codebase

- Strip trailing whitespace from all Python files
- Fix import sorting (I001) across all modules
- Convert Optional[X] to X | None syntax (UP045)
- Remove unused imports (F401)
- Convert lambda assignments to def functions (E731)
- Add TYPE_CHECKING import for forward references
- Update pyproject.toml ruff config:
  - Move select/ignore to [tool.ruff.lint] section
  - Add per-file ignores for DCT colorspace naming (N803/N806)
  - Add per-file ignores for __init__.py import structure (E402)
  - Exclude defunct test_routes.py
- Remove frontends/web/test_routes.py (defunct debug snippet)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-02 17:17:38 -05:00
parent d94ee7be90
commit 6b21190f97
36 changed files with 2275 additions and 2383 deletions

View File

@@ -10,9 +10,9 @@ CHANGES in v4.0.0:
Usage:
from subprocess_stego import SubprocessStego
stego = SubprocessStego()
# Encode with channel key
result = stego.encode(
carrier_data=carrier_bytes,
@@ -23,13 +23,13 @@ Usage:
embed_mode="dct",
channel_key="auto", # or "none", or explicit key
)
if result.success:
stego_bytes = result.stego_data
extension = result.extension
else:
error_message = result.error
# Decode
result = stego.decode(
stego_data=stego_bytes,
@@ -38,19 +38,18 @@ Usage:
pin="123456",
channel_key="auto",
)
# Compare modes (capacity)
result = stego.compare_modes(carrier_bytes)
"""
import json
import base64
import json
import subprocess
import sys
from pathlib import Path
from dataclasses import dataclass
from typing import Optional, Dict, Any, Union
from pathlib import Path
from typing import Any
# Default timeout for operations (seconds)
DEFAULT_TIMEOUT = 120
@@ -63,14 +62,14 @@ WORKER_SCRIPT = Path(__file__).parent / 'stego_worker.py'
class EncodeResult:
"""Result from encode operation."""
success: bool
stego_data: Optional[bytes] = None
filename: Optional[str] = None
stats: Optional[Dict[str, Any]] = None
stego_data: bytes | None = None
filename: str | None = None
stats: dict[str, Any] | None = None
# Channel info (v4.0.0)
channel_mode: Optional[str] = None
channel_fingerprint: Optional[str] = None
error: Optional[str] = None
error_type: Optional[str] = None
channel_mode: str | None = None
channel_fingerprint: str | None = None
error: str | None = None
error_type: str | None = None
@dataclass
@@ -78,12 +77,12 @@ class DecodeResult:
"""Result from decode operation."""
success: bool
is_file: bool = False
message: Optional[str] = None
file_data: Optional[bytes] = None
filename: Optional[str] = None
mime_type: Optional[str] = None
error: Optional[str] = None
error_type: Optional[str] = None
message: str | None = None
file_data: bytes | None = None
filename: str | None = None
mime_type: str | None = None
error: str | None = None
error_type: str | None = None
@dataclass
@@ -92,9 +91,9 @@ class CompareResult:
success: bool
width: int = 0
height: int = 0
lsb: Optional[Dict[str, Any]] = None
dct: Optional[Dict[str, Any]] = None
error: Optional[str] = None
lsb: dict[str, Any] | None = None
dct: dict[str, Any] | None = None
error: str | None = None
@dataclass
@@ -107,38 +106,38 @@ class CapacityResult:
usage_percent: float = 0.0
headroom: int = 0
mode: str = ""
error: Optional[str] = None
error: str | None = None
@dataclass
@dataclass
class ChannelStatusResult:
"""Result from channel status check (v4.0.0)."""
success: bool
mode: str = "public"
configured: bool = False
fingerprint: Optional[str] = None
source: Optional[str] = None
key: Optional[str] = None
error: Optional[str] = None
fingerprint: str | None = None
source: str | None = None
key: str | None = None
error: str | None = None
class SubprocessStego:
"""
Subprocess-isolated steganography operations.
All operations run in a separate Python process. If jpegio or scipy
crashes, only the subprocess dies - Flask keeps running.
"""
def __init__(
self,
worker_path: Optional[Path] = None,
python_executable: Optional[str] = None,
worker_path: Path | None = None,
python_executable: str | None = None,
timeout: int = DEFAULT_TIMEOUT,
):
"""
Initialize subprocess wrapper.
Args:
worker_path: Path to stego_worker.py (default: same directory)
python_executable: Python interpreter to use (default: same as current)
@@ -147,24 +146,24 @@ class SubprocessStego:
self.worker_path = worker_path or WORKER_SCRIPT
self.python = python_executable or sys.executable
self.timeout = timeout
if not self.worker_path.exists():
raise FileNotFoundError(f"Worker script not found: {self.worker_path}")
def _run_worker(self, params: Dict[str, Any], timeout: Optional[int] = None) -> Dict[str, Any]:
def _run_worker(self, params: dict[str, Any], timeout: int | None = None) -> dict[str, Any]:
"""
Run the worker subprocess with given parameters.
Args:
params: Dictionary of parameters (will be JSON-encoded)
timeout: Operation timeout in seconds
Returns:
Dictionary with results from worker
"""
timeout = timeout or self.timeout
input_json = json.dumps(params)
try:
result = subprocess.run(
[self.python, str(self.worker_path)],
@@ -174,7 +173,7 @@ class SubprocessStego:
timeout=timeout,
cwd=str(self.worker_path.parent),
)
if result.returncode != 0:
# Worker crashed
return {
@@ -182,16 +181,16 @@ class SubprocessStego:
'error': f'Worker crashed (exit code {result.returncode})',
'stderr': result.stderr,
}
if not result.stdout.strip():
return {
'success': False,
'error': 'Worker returned empty output',
'stderr': result.stderr,
}
return json.loads(result.stdout)
except subprocess.TimeoutExpired:
return {
'success': False,
@@ -210,29 +209,29 @@ class SubprocessStego:
'error': str(e),
'error_type': type(e).__name__,
}
def encode(
self,
carrier_data: bytes,
reference_data: bytes,
message: Optional[str] = None,
file_data: Optional[bytes] = None,
file_name: Optional[str] = None,
file_mime: Optional[str] = None,
message: str | None = None,
file_data: bytes | None = None,
file_name: str | None = None,
file_mime: str | None = None,
passphrase: str = "",
pin: Optional[str] = None,
rsa_key_data: Optional[bytes] = None,
rsa_password: Optional[str] = None,
pin: str | None = None,
rsa_key_data: bytes | None = None,
rsa_password: str | None = None,
embed_mode: str = "lsb",
dct_output_format: str = "png",
dct_color_mode: str = "color",
# Channel key (v4.0.0)
channel_key: Optional[str] = "auto",
timeout: Optional[int] = None,
channel_key: str | None = "auto",
timeout: int | None = None,
) -> EncodeResult:
"""
Encode a message or file into an image.
Args:
carrier_data: Carrier image bytes
reference_data: Reference photo bytes
@@ -249,7 +248,7 @@ class SubprocessStego:
dct_color_mode: 'grayscale' or 'color' (for DCT mode)
channel_key: 'auto' (server config), 'none' (public), or explicit key (v4.0.0)
timeout: Operation timeout in seconds
Returns:
EncodeResult with stego_data and extension on success
"""
@@ -265,18 +264,18 @@ class SubprocessStego:
'dct_color_mode': dct_color_mode,
'channel_key': channel_key, # v4.0.0
}
if file_data:
params['file_b64'] = base64.b64encode(file_data).decode('ascii')
params['file_name'] = file_name
params['file_mime'] = file_mime
if rsa_key_data:
params['rsa_key_b64'] = base64.b64encode(rsa_key_data).decode('ascii')
params['rsa_password'] = rsa_password
result = self._run_worker(params, timeout)
if result.get('success'):
return EncodeResult(
success=True,
@@ -292,23 +291,23 @@ class SubprocessStego:
error=result.get('error', 'Unknown error'),
error_type=result.get('error_type'),
)
def decode(
self,
stego_data: bytes,
reference_data: bytes,
passphrase: str = "",
pin: Optional[str] = None,
rsa_key_data: Optional[bytes] = None,
rsa_password: Optional[str] = None,
pin: str | None = None,
rsa_key_data: bytes | None = None,
rsa_password: str | None = None,
embed_mode: str = "auto",
# Channel key (v4.0.0)
channel_key: Optional[str] = "auto",
timeout: Optional[int] = None,
channel_key: str | None = "auto",
timeout: int | None = None,
) -> DecodeResult:
"""
Decode a message or file from a stego image.
Args:
stego_data: Stego image bytes
reference_data: Reference photo bytes
@@ -319,7 +318,7 @@ class SubprocessStego:
embed_mode: 'auto', 'lsb', or 'dct'
channel_key: 'auto' (server config), 'none' (public), or explicit key (v4.0.0)
timeout: Operation timeout in seconds
Returns:
DecodeResult with message or file_data on success
"""
@@ -332,13 +331,13 @@ class SubprocessStego:
'embed_mode': embed_mode,
'channel_key': channel_key, # v4.0.0
}
if rsa_key_data:
params['rsa_key_b64'] = base64.b64encode(rsa_key_data).decode('ascii')
params['rsa_password'] = rsa_password
result = self._run_worker(params, timeout)
if result.get('success'):
if result.get('is_file'):
return DecodeResult(
@@ -360,19 +359,19 @@ class SubprocessStego:
error=result.get('error', 'Unknown error'),
error_type=result.get('error_type'),
)
def compare_modes(
self,
carrier_data: bytes,
timeout: Optional[int] = None,
timeout: int | None = None,
) -> CompareResult:
"""
Compare LSB and DCT capacity for a carrier image.
Args:
carrier_data: Carrier image bytes
timeout: Operation timeout in seconds
Returns:
CompareResult with capacity information
"""
@@ -380,9 +379,9 @@ class SubprocessStego:
'operation': 'compare',
'carrier_b64': base64.b64encode(carrier_data).decode('ascii'),
}
result = self._run_worker(params, timeout)
if result.get('success'):
comparison = result.get('comparison', {})
return CompareResult(
@@ -397,23 +396,23 @@ class SubprocessStego:
success=False,
error=result.get('error', 'Unknown error'),
)
def check_capacity(
self,
carrier_data: bytes,
payload_size: int,
embed_mode: str = "lsb",
timeout: Optional[int] = None,
timeout: int | None = None,
) -> CapacityResult:
"""
Check if a payload will fit in the carrier.
Args:
carrier_data: Carrier image bytes
payload_size: Size of payload in bytes
embed_mode: 'lsb' or 'dct'
timeout: Operation timeout in seconds
Returns:
CapacityResult with fit information
"""
@@ -423,9 +422,9 @@ class SubprocessStego:
'payload_size': payload_size,
'embed_mode': embed_mode,
}
result = self._run_worker(params, timeout)
if result.get('success'):
r = result.get('result', {})
return CapacityResult(
@@ -442,19 +441,19 @@ class SubprocessStego:
success=False,
error=result.get('error', 'Unknown error'),
)
def get_channel_status(
self,
reveal: bool = False,
timeout: Optional[int] = None,
timeout: int | None = None,
) -> ChannelStatusResult:
"""
Get current channel key status (v4.0.0).
Args:
reveal: Include full key in response
timeout: Operation timeout in seconds
Returns:
ChannelStatusResult with channel info
"""
@@ -462,9 +461,9 @@ class SubprocessStego:
'operation': 'channel_status',
'reveal': reveal,
}
result = self._run_worker(params, timeout)
if result.get('success'):
status = result.get('status', {})
return ChannelStatusResult(
@@ -483,7 +482,7 @@ class SubprocessStego:
# Convenience function for quick usage
_default_stego: Optional[SubprocessStego] = None
_default_stego: SubprocessStego | None = None
def get_subprocess_stego() -> SubprocessStego: