backend: remove __main__ runner; typing: fix mypy across core and routes; tooling: use uv for pytest in pre-commit; security: narrow broad except in config; mypy: relax untyped decorator check for backend.app.main
This commit is contained in:
@@ -207,7 +207,7 @@ class Settings:
|
||||
parsed = self._parse_list("CORS_ORIGINS", [])
|
||||
if parsed:
|
||||
return parsed
|
||||
except Exception:
|
||||
except (ValueError, SyntaxError, json.JSONDecodeError, TypeError):
|
||||
# Fall back to comma-separated parsing if JSON/literal parsing fails
|
||||
pass
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class UnitTemplate:
|
||||
class WebApplicationTemplate(UnitTemplate):
|
||||
"""Template for web application services."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
name="webapp",
|
||||
description="Web application service (Node.js, Python, etc.)",
|
||||
@@ -159,7 +159,7 @@ class WebApplicationTemplate(UnitTemplate):
|
||||
class DatabaseTemplate(UnitTemplate):
|
||||
"""Template for database services."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
name="database",
|
||||
description="Database service (PostgreSQL, MySQL, MongoDB, etc.)",
|
||||
@@ -249,7 +249,7 @@ class DatabaseTemplate(UnitTemplate):
|
||||
class BackupTimerTemplate(UnitTemplate):
|
||||
"""Template for backup timer services."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
name="backup-timer",
|
||||
description="Scheduled backup service with timer",
|
||||
@@ -334,7 +334,7 @@ class BackupTimerTemplate(UnitTemplate):
|
||||
class ProxySocketTemplate(UnitTemplate):
|
||||
"""Template for socket-activated proxy services."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
name="proxy-socket",
|
||||
description="Socket-activated proxy service",
|
||||
@@ -436,7 +436,7 @@ class ProxySocketTemplate(UnitTemplate):
|
||||
class ContainerTemplate(UnitTemplate):
|
||||
"""Template for containerized services (Docker/Podman)."""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__(
|
||||
name="container",
|
||||
description="Containerized service (Docker/Podman)",
|
||||
@@ -596,11 +596,11 @@ class ContainerTemplate(UnitTemplate):
|
||||
class TemplateRegistry:
|
||||
"""Registry for managing available unit file templates."""
|
||||
|
||||
def __init__(self):
|
||||
self._templates = {}
|
||||
def __init__(self) -> None:
|
||||
self._templates: Dict[str, UnitTemplate] = {}
|
||||
self._register_default_templates()
|
||||
|
||||
def _register_default_templates(self):
|
||||
def _register_default_templates(self) -> None:
|
||||
"""Register all default templates."""
|
||||
templates = [
|
||||
WebApplicationTemplate(),
|
||||
@@ -613,7 +613,7 @@ class TemplateRegistry:
|
||||
for template in templates:
|
||||
self.register(template)
|
||||
|
||||
def register(self, template: UnitTemplate):
|
||||
def register(self, template: UnitTemplate) -> None:
|
||||
"""Register a new template."""
|
||||
self._templates[template.name] = template
|
||||
|
||||
@@ -636,7 +636,7 @@ class TemplateRegistry:
|
||||
def search(self, query: str) -> List[UnitTemplate]:
|
||||
"""Search templates by name, description, or tags."""
|
||||
query = query.lower()
|
||||
results = []
|
||||
results: List[UnitTemplate] = []
|
||||
|
||||
for template in self._templates.values():
|
||||
# Search in name and description
|
||||
|
||||
@@ -9,7 +9,7 @@ import configparser
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
class UnitType(Enum):
|
||||
@@ -46,6 +46,11 @@ class UnitFileInfo:
|
||||
conflicts: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class CaseConfigParser(configparser.ConfigParser):
|
||||
def optionxform(self, optionstr: str) -> str:
|
||||
return str(optionstr)
|
||||
|
||||
|
||||
class SystemdUnitFile:
|
||||
"""
|
||||
Parser and validator for systemd unit files.
|
||||
@@ -306,7 +311,7 @@ class SystemdUnitFile:
|
||||
}
|
||||
|
||||
# Service types and their requirements
|
||||
SERVICE_TYPES = {
|
||||
SERVICE_TYPES: Dict[str, Dict[str, List[str]]] = {
|
||||
"simple": {"required": ["ExecStart"], "conflicts": ["BusName", "Type=forking"]},
|
||||
"exec": {"required": ["ExecStart"], "conflicts": ["BusName"]},
|
||||
"forking": {"recommended": ["PIDFile"], "conflicts": ["BusName"]},
|
||||
@@ -320,11 +325,10 @@ class SystemdUnitFile:
|
||||
"""Initialize with either content string or file path."""
|
||||
self.content = content or ""
|
||||
self.file_path = file_path
|
||||
self.config = configparser.ConfigParser(
|
||||
self.config = CaseConfigParser(
|
||||
interpolation=None, allow_no_value=True, delimiters=("=",)
|
||||
)
|
||||
self.config.optionxform = lambda optionstr: str(optionstr) # Preserve case
|
||||
self._parse_errors = []
|
||||
self._parse_errors: List[ValidationError] = []
|
||||
|
||||
if content:
|
||||
self._parse_content(content)
|
||||
@@ -419,7 +423,7 @@ class SystemdUnitFile:
|
||||
|
||||
def validate(self) -> List[ValidationError]:
|
||||
"""Validate the unit file and return list of errors/warnings."""
|
||||
errors = self._parse_errors.copy()
|
||||
errors: List[ValidationError] = self._parse_errors.copy()
|
||||
|
||||
# Check for basic structure
|
||||
if not self.config.sections():
|
||||
@@ -443,7 +447,7 @@ class SystemdUnitFile:
|
||||
|
||||
def _validate_section(self, section: str) -> List[ValidationError]:
|
||||
"""Validate a specific section."""
|
||||
errors = []
|
||||
errors: List[ValidationError] = []
|
||||
|
||||
# Check if section is known
|
||||
if section not in self.COMMON_SECTIONS:
|
||||
@@ -477,7 +481,7 @@ class SystemdUnitFile:
|
||||
self, section: str, key: str, value: str
|
||||
) -> List[ValidationError]:
|
||||
"""Validate specific key-value pairs."""
|
||||
errors = []
|
||||
errors: List[ValidationError] = []
|
||||
|
||||
# Service-specific validations
|
||||
if section == "Service":
|
||||
@@ -574,7 +578,7 @@ class SystemdUnitFile:
|
||||
|
||||
def _validate_unit_type(self, unit_type: UnitType) -> List[ValidationError]:
|
||||
"""Perform type-specific validation."""
|
||||
errors = []
|
||||
errors: List[ValidationError] = []
|
||||
|
||||
if unit_type == UnitType.SERVICE:
|
||||
errors.extend(self._validate_service())
|
||||
@@ -589,13 +593,14 @@ class SystemdUnitFile:
|
||||
|
||||
def _validate_service(self) -> List[ValidationError]:
|
||||
"""Validate service-specific requirements."""
|
||||
errors = []
|
||||
errors: List[ValidationError] = []
|
||||
|
||||
if not self.config.has_section("Service"):
|
||||
return errors
|
||||
|
||||
service_type = self.config.get("Service", "Type", fallback="simple")
|
||||
type_config = self.SERVICE_TYPES.get(service_type, {})
|
||||
tmp = self.SERVICE_TYPES.get(service_type)
|
||||
type_config: Dict[str, List[str]] = {} if tmp is None else tmp
|
||||
|
||||
# Check required keys
|
||||
for required_key in type_config.get("required", []):
|
||||
@@ -648,7 +653,7 @@ class SystemdUnitFile:
|
||||
|
||||
def _validate_timer(self) -> List[ValidationError]:
|
||||
"""Validate timer-specific requirements."""
|
||||
errors = []
|
||||
errors: List[ValidationError] = []
|
||||
|
||||
if not self.config.has_section("Timer"):
|
||||
return errors
|
||||
@@ -678,7 +683,7 @@ class SystemdUnitFile:
|
||||
|
||||
def _validate_socket(self) -> List[ValidationError]:
|
||||
"""Validate socket-specific requirements."""
|
||||
errors = []
|
||||
errors: List[ValidationError] = []
|
||||
|
||||
if not self.config.has_section("Socket"):
|
||||
return errors
|
||||
@@ -709,7 +714,7 @@ class SystemdUnitFile:
|
||||
|
||||
def _validate_mount(self) -> List[ValidationError]:
|
||||
"""Validate mount-specific requirements."""
|
||||
errors = []
|
||||
errors: List[ValidationError] = []
|
||||
|
||||
if not self.config.has_section("Mount"):
|
||||
return errors
|
||||
@@ -755,7 +760,9 @@ class SystemdUnitFile:
|
||||
for section in self.config.sections():
|
||||
lines.append(f"[{section}]")
|
||||
for key in self.config.options(section):
|
||||
value = self.config.get(section, key)
|
||||
from typing import cast
|
||||
|
||||
value = cast(Optional[str], self.config.get(section, key))
|
||||
if value is None:
|
||||
lines.append(key)
|
||||
else:
|
||||
@@ -796,7 +803,7 @@ class SystemdUnitFile:
|
||||
return self.config.options(section)
|
||||
|
||||
|
||||
def create_unit_file(unit_type: UnitType, **kwargs) -> SystemdUnitFile:
|
||||
def create_unit_file(unit_type: UnitType, **kwargs: Any) -> SystemdUnitFile:
|
||||
"""
|
||||
Create a new unit file of the specified type with basic structure.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user