FreedomBox/plinth/diagnostic_check.py
Sunil Mohan Adapa 4b09d91f93
*: Add type hints for diagnose method
Helps: #2410.

- Ensure that diagnostics methods and parameters are type checked so that we can
catch any potential issues.

- Move plinth/modules/diagnostics/check.py to plinth/diagnostic_check.py to
avoid many circular dependencies created. This is due to
plinth.modules.diagnostics automatically imported when
plinth.modules.diagnostics.check is imported. Also app.py is already (type)
dependent on diagnostic_check due to diagnose() method. To make the Check
classes independent of diagnostic module is okay.

Tests:

- Run make check-type.

- Run full diagnostics with following apps installed: torproxy, tor.
  - Test to netcat to 9051 in tor works.
  - Test 'port available for internal/external networks' in firewall works.
  - Test 'Package is latest' works.
  - Test 'Access url with proxy' in privoxy works.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
[jvalleroy: Also move tests for diagnostic_check]
Signed-off-by: James Valleroy <jvalleroy@mailbox.org>
2024-03-09 14:23:33 -05:00

71 lines
1.9 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Diagnostic check data type."""
import dataclasses
import json
from dataclasses import dataclass, field
from enum import StrEnum
from typing import TypeAlias
from django.utils.translation import gettext
from plinth.utils import SafeFormatter
DiagnosticCheckParameters: TypeAlias = dict[str, str | int | bool | None]
class Result(StrEnum):
"""The result of a diagnostic check."""
NOT_DONE = 'not_done'
PASSED = 'passed'
WARNING = 'warning'
FAILED = 'failed'
ERROR = 'error'
@dataclass
class DiagnosticCheck:
"""A diagnostic check and optional result and parameters."""
check_id: str
description: str
result: Result = Result.NOT_DONE
parameters: DiagnosticCheckParameters = field(default_factory=dict)
@property
def translated_description(self):
"""Return translated string for description."""
description = gettext(self.description)
if self.parameters:
return SafeFormatter().vformat(description, [], self.parameters)
return description
class CheckJSONEncoder(json.JSONEncoder):
"""Encode objects that include DiagnosticChecks."""
def default(self, o):
"""Add class tag to DiagnosticChecks."""
if isinstance(o, DiagnosticCheck):
o = dataclasses.asdict(o)
o.update({'__class__': 'DiagnosticCheck'})
return o
return super().default(o)
class CheckJSONDecoder(json.JSONDecoder):
"""Decode objects that include DiagnosticChecks."""
def __init__(self):
json.JSONDecoder.__init__(self, object_hook=CheckJSONDecoder.from_dict)
@staticmethod
def from_dict(data):
"""Convert tagged data to DiagnosticCheck."""
if data.get('__class__') == 'DiagnosticCheck':
return DiagnosticCheck(data['check_id'], data['description'],
data['result'], data['parameters'])
return data