Coverage for src / anpr2mqtt / tools.py: 96%
40 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-08 17:29 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-08 17:29 +0000
1from pathlib import Path
2from typing import TYPE_CHECKING, Literal
4import structlog
5from PIL import Image
6from pydantic import BaseModel
7from pydantic_settings import (
8 BaseSettings,
9 CliApp,
10 CliPositionalArg,
11 CliSubCommand,
12 SettingsConfigDict,
13)
15from anpr2mqtt.event_handler import examine_file, scan_ocr_fields
16from anpr2mqtt.settings import EventSettings, OCRFieldSettings, OCRSettings
18if TYPE_CHECKING:
19 from anpr2mqtt.const import ImageInfo
21log = structlog.get_logger()
24class OCRTool(BaseModel):
25 event: EventSettings | None = EventSettings()
26 ocr: OCRFieldSettings | None = None
27 log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
28 image_file: CliPositionalArg[str]
30 def cli_cmd(self) -> None:
31 structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(self.log_level))
33 image_path: Path = self.event.watch_path if self.event is not None else Path()
34 image: Image.Image | None = Image.open(image_path / self.image_file)
35 if self.ocr is not None:
36 ocr_settings: OCRSettings = OCRSettings(fields={"hik_direction": self.ocr})
37 else:
38 ocr_settings = OCRSettings()
39 log.debug("ocr_files: ocr_settings->%s", ocr_settings)
40 if image and self.event:
41 print(scan_ocr_fields(image, self.event, ocr_settings)) # noqa: T201
42 else:
43 print("Image can't be loaded") # noqa: T201
46class ListTool(BaseModel):
47 event: EventSettings = EventSettings()
48 log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
50 def cli_cmd(self) -> None:
51 structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(self.log_level))
52 log.info(f"list_dir: {self.event.event} from {self.event.watch_path.resolve()}")
53 for p in self.event.watch_path.iterdir(): # ty:ignore[invalid-argument-type]
54 results: ImageInfo | None = examine_file(p, self.event.image_name_re)
55 if results is not None:
56 print(f"{results.target}: timestamp={results.timestamp},ext={results.ext}") # noqa: T201
59class Tools(BaseSettings, cli_parse_args=True, cli_exit_on_error=True):
60 model_config = SettingsConfigDict(
61 env_nested_delimiter="__",
62 env_ignore_empty=True,
63 cli_avoid_json=True,
64 )
65 ocr_file: CliSubCommand[OCRTool]
66 list_dir: CliSubCommand[ListTool]
68 def cli_cmd(self) -> None:
69 CliApp.run_subcommand(self)
72def tools() -> None:
73 CliApp.run(model_cls=Tools)