From 28acdbe47a8e69d19b6931afd3262c4ffbc04187 Mon Sep 17 00:00:00 2001 From: Ryan Snodgrass Date: Wed, 22 May 2024 21:11:05 -0400 Subject: [PATCH] reformatted with ruff --- .pre-commit-config.yaml | 4 + custom_components/poolmath/client.py | 76 +++++++-------- custom_components/poolmath/const.py | 26 ++--- custom_components/poolmath/sensor.py | 47 ++++----- custom_components/poolmath/targets.py | 134 +++++++++++++------------- 5 files changed, 147 insertions(+), 140 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 641ed3c..ad678f3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,7 @@ +# pre-commit autoupdate + +fail_fast: true + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 diff --git a/custom_components/poolmath/client.py b/custom_components/poolmath/client.py index de05c9e..99343f1 100644 --- a/custom_components/poolmath/client.py +++ b/custom_components/poolmath/client.py @@ -14,33 +14,33 @@ LOG = logging.getLogger(__name__) -DEFAULT_POOL_ID = "unknown" +DEFAULT_POOL_ID = 'unknown' KNOWN_SENSOR_KEYS = [ - "fc", - "cc", - "cya", - "ch", - "ph", - "ta", - "salt", - "bor", - "tds", - "csi", - "waterTemp", - "flowRate", - "pressure", - "swgCellPercent", + 'fc', + 'cc', + 'cya', + 'ch', + 'ph', + 'ta', + 'salt', + 'bor', + 'tds', + 'csi', + 'waterTemp', + 'flowRate', + 'pressure', + 'swgCellPercent', ] ONLY_INCLUDE_IF_TRACKED = { - "salt": "trackSalt", - "bor": "trackBor", - "cc": "trackCC", - "csi": "trackCSI", + 'salt': 'trackSalt', + 'bor': 'trackBor', + 'cc': 'trackCC', + 'csi': 'trackCSI', } -EXAMPLE_URL = "https://api.poolmathapp.com/share/XXXXXX.json" +EXAMPLE_URL = 'https://api.poolmathapp.com/share/XXXXXX.json' class PoolMathClient: @@ -51,16 +51,16 @@ def __init__(self, url, name=DEFAULT_NAME, timeout=DEFAULT_TIMEOUT): # parse out the unique pool identifier from the provided URL (include hyphen for tfp-) self._pool_id = DEFAULT_POOL_ID - match = re.search(r"poolmathapp.com/(mypool|share)/([a-zA-Z0-9-]+)", self._url) + match = re.search(r'poolmathapp.com/(mypool|share)/([a-zA-Z0-9-]+)', self._url) if match: self._pool_id = match[2] else: - LOG.error(f"Invalid URL for PoolMath {self._url}, use {EXAMPLE_URL} format") + LOG.error(f'Invalid URL for PoolMath {self._url}, use {EXAMPLE_URL} format') - self._json_url = f"https://api.poolmathapp.com/share/{self._pool_id}.json" + self._json_url = f'https://api.poolmathapp.com/share/{self._pool_id}.json' if self._json_url != self._url: LOG.warning( - f"Using JSON URL {self._json_url} instead of yaml configured URL {self._url}" + f'Using JSON URL {self._json_url} instead of yaml configured URL {self._url}' ) async def async_update(self): @@ -68,12 +68,12 @@ async def async_update(self): async with httpx.AsyncClient() as client: LOG.info( - f"GET {self._json_url} (timeout={self._timeout}; name={self.name}; id={self.pool_id})" + f'GET {self._json_url} (timeout={self._timeout}; name={self.name}; id={self.pool_id})' ) response = await client.request( - "GET", self._json_url, timeout=self._timeout, follow_redirects=True + 'GET', self._json_url, timeout=self._timeout, follow_redirects=True ) - LOG.debug(f"GET {self._json_url} response: {response.status_code}") + LOG.debug(f'GET {self._json_url} response: {response.status_code}') if response.status_code == httpx.codes.OK: return json.loads(response.text) @@ -87,12 +87,12 @@ async def process_log_entry_callbacks(self, poolmath_json, async_callback): if not poolmath_json: return - pools = poolmath_json.get("pools") + pools = poolmath_json.get('pools') if not pools: return - pool = pools[0].get("pool") - overview = pool.get("overview") + pool = pools[0].get('pool') + overview = pool.get('overview') latest_timestamp = None for measurement in KNOWN_SENSOR_KEYS: @@ -105,11 +105,11 @@ async def process_log_entry_callbacks(self, poolmath_json, async_callback): if measurement in ONLY_INCLUDE_IF_TRACKED: if not pool.get(ONLY_INCLUDE_IF_TRACKED.get(measurement)): LOG.info( - f"Ignoring measurement {measurement} since tracking is disable in PoolMath" + f'Ignoring measurement {measurement} since tracking is disable in PoolMath' ) continue - timestamp = overview.get(f"{measurement}Ts") + timestamp = overview.get(f'{measurement}Ts') # find the timestamp of the most recent measurement update if not latest_timestamp or timestamp > latest_timestamp: @@ -117,18 +117,18 @@ async def process_log_entry_callbacks(self, poolmath_json, async_callback): # add any attributes relevent to this measurement attributes = {} - value_min = pool.get(f"{measurement}Min") + value_min = pool.get(f'{measurement}Min') if value_min: attributes[ATTR_TARGET_MIN] = value_min - value_max = pool.get(f"{measurement}Max") + value_max = pool.get(f'{measurement}Max') if value_max: attributes[ATTR_TARGET_MAX] = value_max - target = pool.get(f"{measurement}Target") + target = pool.get(f'{measurement}Target') if target: - attributes["target"] = target - attributes[ATTR_TARGET_SOURCE] = "PoolMath" + attributes['target'] = target + attributes[ATTR_TARGET_SOURCE] = 'PoolMath' # update the sensor await async_callback( @@ -151,4 +151,4 @@ def url(self): @staticmethod def _entry_timestamp(entry): - return entry.find("time", class_="timestamp timereal").text + return entry.find('time', class_='timestamp timereal').text diff --git a/custom_components/poolmath/const.py b/custom_components/poolmath/const.py index cf4abb5..66aab6c 100644 --- a/custom_components/poolmath/const.py +++ b/custom_components/poolmath/const.py @@ -1,21 +1,21 @@ """Constants for Pool Math.""" -DOMAIN = "poolmath" +DOMAIN = 'poolmath' -ATTRIBUTION = "Data by PoolMath (Trouble Free Pool)" +ATTRIBUTION = 'Data by PoolMath (Trouble Free Pool)' -ATTR_ATTRIBUTION = "attribution" -ATTR_DESCRIPTION = "description" -ATTR_LAST_UPDATED_TIME = "last_updated" -ATTR_TARGET_MIN = "target_min" -ATTR_TARGET_MAX = "target_max" -ATTR_TARGET_SOURCE = "target_source" +ATTR_ATTRIBUTION = 'attribution' +ATTR_DESCRIPTION = 'description' +ATTR_LAST_UPDATED_TIME = 'last_updated' +ATTR_TARGET_MIN = 'target_min' +ATTR_TARGET_MAX = 'target_max' +ATTR_TARGET_SOURCE = 'target_source' -CONF_TARGET = "target" -CONF_TIMEOUT = "timeout" +CONF_TARGET = 'target' +CONF_TIMEOUT = 'timeout' -DEFAULT_NAME = "Pool" +DEFAULT_NAME = 'Pool' DEFAULT_TIMEOUT = 15.0 -ICON_GAUGE = "mdi:gauge" -ICON_POOL = "mdi:pool" +ICON_GAUGE = 'mdi:gauge' +ICON_POOL = 'mdi:pool' diff --git a/custom_components/poolmath/sensor.py b/custom_components/poolmath/sensor.py index e7df10f..f9b6dba 100644 --- a/custom_components/poolmath/sensor.py +++ b/custom_components/poolmath/sensor.py @@ -9,7 +9,7 @@ ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_URL, - UnitOfTemperature + UnitOfTemperature, ) from homeassistant.core import callback @@ -36,7 +36,7 @@ LOG = logging.getLogger(__name__) -DATA_UPDATED = "poolmath_data_updated" +DATA_UPDATED = 'poolmath_data_updated' SCAN_INTERVAL = timedelta(minutes=15) @@ -47,8 +47,8 @@ vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, # NOTE: targets are not really implemented, other than tfp vol.Optional( - CONF_TARGET, default="tfp" - ): cv.string # targets/*.yaml file with min/max targets + CONF_TARGET, default='tfp' + ): cv.string, # targets/*.yaml file with min/max targets # FIXME: allow specifying EXACTLY which log types to monitor, always create the sensors # vol.Optional(CONF_LOG_TYPES, default=None): } @@ -96,7 +96,7 @@ def __init__( @property def name(self): """Return the name of the sensor.""" - return "Pool Math Service: " + self._name + return 'Pool Math Service: ' + self._name @property def state(self): @@ -119,21 +119,21 @@ async def async_update(self): client = self._poolmath_client poolmath_json = await client.async_update() except Exception as e: - LOG.warning(f"PoolMath request failed! {url}: {e}") + LOG.warning(f'PoolMath request failed! {url}: {e}') return if not poolmath_json: - LOG.warning(f"PoolMath returned NO JSON data: {url}") + LOG.warning(f'PoolMath returned NO JSON data: {url}') return # update state attributes with relevant data - pools = poolmath_json.get("pools") + pools = poolmath_json.get('pools') if not pools: - LOG.warning(f"PoolMath returned EMPTY pool data: {url}") + LOG.warning(f'PoolMath returned EMPTY pool data: {url}') return - pool = pools[0].get("pool") - self._attrs |= {"name": pool.get("name"), "volume": pool.get("volume")} + pool = pools[0].get('pool') + self._attrs |= {'name': pool.get('name'), 'volume': pool.get('volume')} # iterate through all the log entries and update sensor states timestamp = await client.process_log_entry_callbacks( @@ -156,7 +156,7 @@ async def get_sensor_entity(self, sensor_type, poolmath_json): LOG.warning(f"Unknown sensor '{sensor_type}' discovered for {self.name}") return None - name = self._name + " " + config[ATTR_NAME] + name = self._name + ' ' + config[ATTR_NAME] pool_id = self._poolmath_client.pool_id sensor = UpdatableSensor( @@ -176,7 +176,7 @@ async def _update_sensor_callback( sensor = await self.get_sensor_entity(measurement_type, poolmath_json) if sensor and sensor.state != state: LOG.info( - f"{sensor.name} {measurement_type}={state} {sensor.unit_of_measurement} (timestamp={timestamp})" + f'{sensor.name} {measurement_type}={state} {sensor.unit_of_measurement} (timestamp={timestamp})' ) sensor.inject_state(state, timestamp, attributes) @@ -200,7 +200,7 @@ def __init__(self, hass, pool_id, name, config, sensor_type, poolmath_json): self._state = None if pool_id: - self._unique_id = f"poolmath_{pool_id}_{sensor_type}" + self._unique_id = f'poolmath_{pool_id}_{sensor_type}' else: self._unique_id = None @@ -210,26 +210,29 @@ def __init__(self, hass, pool_id, name, config, sensor_type, poolmath_json): # applies to other units). No time to fix now, but perhaps someone will submit a PR # to fix this in future. self._unit_of_measurement = self._config[ATTR_UNIT_OF_MEASUREMENT] - if self._unit_of_measurement in [UnitOfTemperature.FAHRENHEIT, UnitOfTemperature.CELSIUS]: + if self._unit_of_measurement in [ + UnitOfTemperature.FAHRENHEIT, + UnitOfTemperature.CELSIUS, + ]: # inspect the first JSON response to determine things that are not specified # with sensor values (since units/update timestamps are in separate keys # within the JSON doc) - pools = poolmath_json.get("pools") + pools = poolmath_json.get('pools') if pools: - pool = pools[0].get("pool") - if pool.get("waterTempUnitDefault") == 1: + pool = pools[0].get('pool') + if pool.get('waterTempUnitDefault') == 1: self._unit_of_measurement = UnitOfTemperature.CELSIUS else: self._unit_of_measurement = UnitOfTemperature.FAHRENHEIT - LOG.info(f"Unit of temperature measurement {self._unit_of_measurement}") + LOG.info(f'Unit of temperature measurement {self._unit_of_measurement}') # FIXME: use 'targets' configuration value and load appropriate yaml targets_map = get_pool_targets() if targets_map: self._targets = targets_map.get(sensor_type) if self._targets: - self._attrs[ATTR_TARGET_SOURCE] = "tfp" + self._attrs[ATTR_TARGET_SOURCE] = 'tfp' self._attrs.update(self._targets) @property @@ -262,7 +265,7 @@ def extra_state_attributes(self): @property def icon(self): - return self._config["icon"] + return self._config['icon'] def inject_state(self, state, timestamp, attributes): state_changed = self._state != state @@ -294,7 +297,7 @@ async def async_added_to_hass(self) -> None: if not state: return self._state = state.state - LOG.debug(f"Restored sensor {self._name} previous state {self._state}") + LOG.debug(f'Restored sensor {self._name} previous state {self._state}') # restore attributes if ATTR_LAST_UPDATED_TIME in state.attributes: diff --git a/custom_components/poolmath/targets.py b/custom_components/poolmath/targets.py index 175eb1e..ead864c 100644 --- a/custom_components/poolmath/targets.py +++ b/custom_components/poolmath/targets.py @@ -19,111 +19,111 @@ # FIXME: add strings translation support for names/descriptiongs/units? # see https://www.troublefreepool.com/blog/2018/12/12/abcs-of-pool-water-chemistry/ POOL_MATH_SENSOR_SETTINGS = { - "cc": { - ATTR_NAME: "CC", - ATTR_UNIT_OF_MEASUREMENT: "mg/L", - ATTR_DESCRIPTION: "Combined Chlorine", + 'cc': { + ATTR_NAME: 'CC', + ATTR_UNIT_OF_MEASUREMENT: 'mg/L', + ATTR_DESCRIPTION: 'Combined Chlorine', ATTR_ICON: ICON_GAUGE, }, - "fc": { - ATTR_NAME: "FC", - ATTR_UNIT_OF_MEASUREMENT: "mg/L", - ATTR_DESCRIPTION: "Free Chlorine", + 'fc': { + ATTR_NAME: 'FC', + ATTR_UNIT_OF_MEASUREMENT: 'mg/L', + ATTR_DESCRIPTION: 'Free Chlorine', ATTR_ICON: ICON_GAUGE, }, - "ph": { - ATTR_NAME: "pH", - ATTR_UNIT_OF_MEASUREMENT: "pH", - ATTR_DESCRIPTION: "Acidity/Basicity", + 'ph': { + ATTR_NAME: 'pH', + ATTR_UNIT_OF_MEASUREMENT: 'pH', + ATTR_DESCRIPTION: 'Acidity/Basicity', ATTR_ICON: ICON_GAUGE, }, - "ta": { - ATTR_NAME: "TA", - ATTR_UNIT_OF_MEASUREMENT: "ppm", - ATTR_DESCRIPTION: "Total Alkalinity", + 'ta': { + ATTR_NAME: 'TA', + ATTR_UNIT_OF_MEASUREMENT: 'ppm', + ATTR_DESCRIPTION: 'Total Alkalinity', ATTR_ICON: ICON_GAUGE, }, - "ch": { - ATTR_NAME: "CH", - ATTR_UNIT_OF_MEASUREMENT: "ppm", - ATTR_DESCRIPTION: "Calcium Hardness", + 'ch': { + ATTR_NAME: 'CH', + ATTR_UNIT_OF_MEASUREMENT: 'ppm', + ATTR_DESCRIPTION: 'Calcium Hardness', ATTR_ICON: ICON_GAUGE, }, - "cya": { - ATTR_NAME: "CYA", - ATTR_UNIT_OF_MEASUREMENT: "ppm", - ATTR_DESCRIPTION: "Cyanuric Acid", + 'cya': { + ATTR_NAME: 'CYA', + ATTR_UNIT_OF_MEASUREMENT: 'ppm', + ATTR_DESCRIPTION: 'Cyanuric Acid', ATTR_ICON: ICON_GAUGE, }, - "salt": { - ATTR_NAME: "Salt", - ATTR_UNIT_OF_MEASUREMENT: "ppm", - ATTR_DESCRIPTION: "Salt", + 'salt': { + ATTR_NAME: 'Salt', + ATTR_UNIT_OF_MEASUREMENT: 'ppm', + ATTR_DESCRIPTION: 'Salt', ATTR_ICON: ICON_GAUGE, }, - "bor": { - ATTR_NAME: "Borate", - ATTR_UNIT_OF_MEASUREMENT: "ppm", - ATTR_DESCRIPTION: "Borate", + 'bor': { + ATTR_NAME: 'Borate', + ATTR_UNIT_OF_MEASUREMENT: 'ppm', + ATTR_DESCRIPTION: 'Borate', ATTR_ICON: ICON_GAUGE, }, - "borate": { - ATTR_NAME: "Borate", - ATTR_UNIT_OF_MEASUREMENT: "ppm", - ATTR_DESCRIPTION: "Borate", + 'borate': { + ATTR_NAME: 'Borate', + ATTR_UNIT_OF_MEASUREMENT: 'ppm', + ATTR_DESCRIPTION: 'Borate', ATTR_ICON: ICON_GAUGE, }, - "csi": { - ATTR_NAME: "CSI", - ATTR_UNIT_OF_MEASUREMENT: "CSI", - ATTR_DESCRIPTION: "Calcite Saturation Index", + 'csi': { + ATTR_NAME: 'CSI', + ATTR_UNIT_OF_MEASUREMENT: 'CSI', + ATTR_DESCRIPTION: 'Calcite Saturation Index', ATTR_ICON: ICON_GAUGE, }, - "temp": { - ATTR_NAME: "Temp", + 'temp': { + ATTR_NAME: 'Temp', ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT, - ATTR_DESCRIPTION: "Temperature", - ATTR_ICON: "mdi:coolant-temperature", + ATTR_DESCRIPTION: 'Temperature', + ATTR_ICON: 'mdi:coolant-temperature', }, - "waterTemp": { - ATTR_NAME: "Temp", + 'waterTemp': { + ATTR_NAME: 'Temp', ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT, - ATTR_DESCRIPTION: "Temperature", - ATTR_ICON: "mdi:coolant-temperature", + ATTR_DESCRIPTION: 'Temperature', + ATTR_ICON: 'mdi:coolant-temperature', }, - "swgCellPercentage": { - ATTR_NAME: "SWG Cell", - ATTR_UNIT_OF_MEASUREMENT: "%", - ATTR_DESCRIPTION: "SWG Cell Percentage", - ATTR_ICON: "mdi:coolant-temperature", + 'swgCellPercentage': { + ATTR_NAME: 'SWG Cell', + ATTR_UNIT_OF_MEASUREMENT: '%', + ATTR_DESCRIPTION: 'SWG Cell Percentage', + ATTR_ICON: 'mdi:coolant-temperature', }, - "pressure": { - ATTR_NAME: "Pressure", + 'pressure': { + ATTR_NAME: 'Pressure', ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT, - ATTR_DESCRIPTION: "Filter Pressure", - ATTR_ICON: "mdi:coolant-temperature", + ATTR_DESCRIPTION: 'Filter Pressure', + ATTR_ICON: 'mdi:coolant-temperature', }, - "flowRate": { - ATTR_NAME: "Flow Rate", - ATTR_UNIT_OF_MEASUREMENT: "gpm", # FIXME: confirm units - ATTR_DESCRIPTION: "Flow Rate", - ATTR_ICON: "mdi:coolant-temperature", + 'flowRate': { + ATTR_NAME: 'Flow Rate', + ATTR_UNIT_OF_MEASUREMENT: 'gpm', # FIXME: confirm units + ATTR_DESCRIPTION: 'Flow Rate', + ATTR_ICON: 'mdi:coolant-temperature', }, } # FIXME: targets should be profiles that users can select from based on the needs # for a specific body of water (pool, salt pool, spa, hot tub, pond, etc) -TFP_TARGET_NAME = "tfp" +TFP_TARGET_NAME = 'tfp' # FIXME: targets should probably all be in code, since some values are computed based on other values TFP_RECOMMENDED_TARGET_LEVELS = { - "cc": {ATTR_TARGET_MIN: 0, ATTR_TARGET_MAX: 0.1}, - "ph": {ATTR_TARGET_MIN: 7.2, ATTR_TARGET_MAX: 7.8, "target": 7.4}, - "ta": {ATTR_TARGET_MIN: 50, ATTR_TARGET_MAX: 90}, + 'cc': {ATTR_TARGET_MIN: 0, ATTR_TARGET_MAX: 0.1}, + 'ph': {ATTR_TARGET_MIN: 7.2, ATTR_TARGET_MAX: 7.8, 'target': 7.4}, + 'ta': {ATTR_TARGET_MIN: 50, ATTR_TARGET_MAX: 90}, # 'ch': { ATTR_TARGET_MIN: 250, ATTR_TARGET_MAX: 650 }, # with salt: 350-450 ppm # 'cya': { ATTR_TARGET_MIN: 30, ATTR_TARGET_MAX: 50 }, # with salt: 70-80 ppm - "salt": {ATTR_TARGET_MIN: 3000, ATTR_TARGET_MAX: 3200, "target": 3100}, + 'salt': {ATTR_TARGET_MIN: 3000, ATTR_TARGET_MAX: 3200, 'target': 3100}, } DEFAULT_TARGETS = TFP_TARGET_NAME