Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: sample reading lifted out of metrics presentation #12

Merged
merged 1 commit into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions py_air_control_exporter/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from py_air_control_exporter import metrics


def create_app(targets: dict[str, metrics.Target]):
def create_app(readings_source: metrics.ReadingsSource):
app = Flask(__name__)
metrics_collector_registry = prometheus_client.CollectorRegistry(auto_describe=True)
metrics_collector_registry.register(metrics.PyAirControlCollector(targets))
metrics_collector_registry.register(metrics.PyAirControlCollector(readings_source))
app.wsgi_app = DispatcherMiddleware(
app.wsgi_app,
{"/metrics": prometheus_client.make_wsgi_app(metrics_collector_registry)},
Expand Down
25 changes: 25 additions & 0 deletions py_air_control_exporter/fetcher_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from collections.abc import Iterable

from py_air_control_exporter import fetchers_api
from py_air_control_exporter.fetchers import http_philips

_KNOWN_FETCHERS: dict[str, fetchers_api.FetcherCreator] = {
"http": http_philips.create_fetcher,
}


class UnknownProtocolError(Exception):
pass


def get_known_protocols() -> Iterable[str]:
return _KNOWN_FETCHERS.keys()


def create_fetcher(
protocol: str, fetcher_config: fetchers_api.FetcherCreatorArgs
) -> fetchers_api.Fetcher:
fetcher_creator = _KNOWN_FETCHERS.get(protocol)
if fetcher_creator is None:
raise UnknownProtocolError
return fetcher_creator(fetcher_config)
6 changes: 0 additions & 6 deletions py_air_control_exporter/fetchers/fetcher_registry.py

This file was deleted.

9 changes: 5 additions & 4 deletions py_air_control_exporter/fetchers/http_philips.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,27 @@ def create_fetcher(config: fetchers_api.FetcherCreatorArgs) -> fetchers_api.Fetc
return lambda target_host=config.target_host: get_reading(target_host)


def get_reading(
host: str,
) -> fetchers_api.TargetReading | None:
def get_reading(host: str) -> fetchers_api.TargetReading:
client = http_client.HTTPAirClient(host)

try:
status_data = client.get_status() or {}
filters_data = client.get_filters() or {}

return fetchers_api.TargetReading(
host=host,
has_errors=False,
air_quality=create_air_quality(status_data),
control_info=create_control_info(status_data),
filters=create_filter_info(filters_data),
)

except Exception as ex:
LOG.error(
"Could not read values from air control device %s. Error: %s", host, ex
)
LOG.debug("Exception stack trace:", exc_info=True)
return None
return fetchers_api.TargetReading(host=host, has_errors=True)


def create_air_quality(status_data: dict) -> fetchers_api.AirQuality:
Expand Down
10 changes: 6 additions & 4 deletions py_air_control_exporter/fetchers_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ class Filter:

@dataclass(frozen=True)
class TargetReading:
air_quality: AirQuality | None
control_info: ControlInfo | None
filters: dict[str, Filter] | None
host: str
has_errors: bool = False
air_quality: AirQuality | None = None
control_info: ControlInfo | None = None
filters: dict[str, Filter] | None = None


Fetcher = Callable[[], TargetReading | None]
Fetcher = Callable[[], TargetReading]


@dataclass(frozen=True)
Expand Down
61 changes: 47 additions & 14 deletions py_air_control_exporter/main.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import logging
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import click
import yaml

from py_air_control_exporter import app, fetchers_api, metrics
from py_air_control_exporter.fetchers import fetcher_registry
from py_air_control_exporter import app, fetcher_registry, fetchers_api, metrics


@dataclass(frozen=True)
class Target:
host: str
fetcher: fetchers_api.Fetcher


LOG = logging.getLogger(__name__)

Expand All @@ -34,8 +41,7 @@
"--protocol",
default="http",
type=click.Choice(
tuple(fetcher_registry.KNOWN_FETCHERS.keys()),
case_sensitive=False,
tuple(fetcher_registry.get_known_protocols()), case_sensitive=False
),
show_default=True,
help="The protocol to use when communicating with the air purifier "
Expand Down Expand Up @@ -76,7 +82,8 @@ def main(host, name, protocol, listen_address, listen_port, config, verbose, qui
LOG.error("No targets specified. Please specify at least one target.")
sys.exit(1)

app.create_app(targets).run(host=listen_address, port=listen_port)
readings_source = create_readings_source(targets)
app.create_app(readings_source).run(host=listen_address, port=listen_port)


def setup_logging(verbosity_level: int) -> None:
Expand All @@ -98,9 +105,36 @@ def load_config(config_path: Path | None) -> dict[str, Any]:
return yaml.safe_load(f)


def create_readings_source(
targets: dict[str, Target],
) -> metrics.ReadingsSource:
def _fetch() -> dict[str, fetchers_api.TargetReading]:
target_readings = {}
for name, target in targets.items():
target_reading = None
try:
target_reading = target.fetcher()
except Exception as ex:
LOG.error(
"Failed to sample the air quality from target '%s'. Error: %s",
name,
ex,
)
LOG.debug("Exception stack trace:", exc_info=True)
continue

if target_reading is None:
continue

target_readings[name] = target_reading
return target_readings

return _fetch


def create_targets(
targets_config: dict[str, dict] | None = None,
) -> dict[str, metrics.Target] | None:
) -> dict[str, Target] | None:
targets = {}

if targets_config:
Expand All @@ -111,19 +145,18 @@ def create_targets(
)
protocol = target_config["protocol"]

if protocol not in fetcher_registry.KNOWN_FETCHERS:
try:
targets[name] = Target(
host=fetcher_config.target_host,
fetcher=fetcher_registry.create_fetcher(protocol, fetcher_config),
)
except fetcher_registry.UnknownProtocolError:
LOG.error(
"Unknown protocol '%s' for target '%s'. Known protocols: %s",
protocol,
name,
", ".join(fetcher_registry.KNOWN_FETCHERS.keys()),
", ".join(fetcher_registry.get_known_protocols()),
)
return None

targets[name] = metrics.Target(
host=fetcher_config.target_host,
name=fetcher_config.target_name,
fetcher=fetcher_registry.KNOWN_FETCHERS[protocol](fetcher_config),
)

return targets
Loading