diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 08775e6..08186ff 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,17 +28,11 @@ jobs: run: | python -m pip install --upgrade pip pip install .[dev] - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --exit-zero - - name: Pylint - run: pylint ./ruuvitag_sensor - - name: Mypy + - name: ruff lint + run: ruff check ./ruuvitag_sensor + - name: mypy run: mypy ./ruuvitag_sensor - - name: isort - run: isort . --diff --check-only - - name: black - run: black . --check + - name: ruff format + run: ruff format --check - name: Tests run: pytest -v -s --show-capture all diff --git a/developer_notes.md b/developer_notes.md index 65f5b48..5c520a8 100644 --- a/developer_notes.md +++ b/developer_notes.md @@ -40,14 +40,9 @@ $ python -m pip install -e .[dev] $ python -m pip install -e .\[dev\] ``` -Flake8 +ruff ```sh -$ flake8 -``` - -Pylint -```sh -$ pylint ./ruuvitag_sensor/ +$ ruff check ``` mypy @@ -57,14 +52,9 @@ $ mypy ./ruuvitag_sensor/ ## Formatting -isort -```sh -$ isort . -``` - -black +ruff ```sh -$ black +$ ruff format --check ``` ## Project files diff --git a/examples/http_server.py b/examples/http_server.py index 433979e..fbeb31e 100644 --- a/examples/http_server.py +++ b/examples/http_server.py @@ -42,7 +42,7 @@ def update_data(): """ Update data sent by background process to global all_data """ - global all_data # pylint: disable=global-variable-not-assigned + global all_data # noqa: PLW0602 while not q.empty(): data = q.get() all_data[data[0]] = data[1] diff --git a/examples/post_to_influxdb.py b/examples/post_to_influxdb.py index 746630f..a22a0d7 100644 --- a/examples/post_to_influxdb.py +++ b/examples/post_to_influxdb.py @@ -50,22 +50,20 @@ def write_to_influxdb(received_data): mac = received_data[0] payload = received_data[1] - dataFormat = payload["data_format"] if ("data_format" in payload) else None + dataFormat = payload.get("data_format", None) fields = {} - fields["temperature"] = payload["temperature"] if ("temperature" in payload) else None - fields["humidity"] = payload["humidity"] if ("humidity" in payload) else None - fields["pressure"] = payload["pressure"] if ("pressure" in payload) else None - fields["accelerationX"] = payload["acceleration_x"] if ("acceleration_x" in payload) else None - fields["accelerationY"] = payload["acceleration_y"] if ("acceleration_y" in payload) else None - fields["accelerationZ"] = payload["acceleration_z"] if ("acceleration_z" in payload) else None + fields["temperature"] = payload.get("temperature", None) + fields["humidity"] = payload.get("humidity", None) + fields["pressure"] = payload.get("pressure", None) + fields["accelerationX"] = payload.get("acceleration_x", None) + fields["accelerationY"] = payload.get("acceleration_y", None) + fields["accelerationZ"] = payload.get("acceleration_z", None) fields["batteryVoltage"] = payload["battery"] / 1000.0 if ("battery" in payload) else None - fields["txPower"] = payload["tx_power"] if ("tx_power" in payload) else None - fields["movementCounter"] = payload["movement_counter"] if ("movement_counter" in payload) else None - fields["measurementSequenceNumber"] = ( - payload["measurement_sequence_number"] if ("measurement_sequence_number" in payload) else None - ) - fields["tagID"] = payload["tagID"] if ("tagID" in payload) else None - fields["rssi"] = payload["rssi"] if ("rssi" in payload) else None + fields["txPower"] = payload.get("tx_power", None) + fields["movementCounter"] = payload.get("movement_counter", None) + fields["measurementSequenceNumber"] = payload.get("measurement_sequence_number", None) + fields["tagID"] = payload.get("tagID", None) + fields["rssi"] = payload.get("rssi", None) json_body = [ {"measurement": "ruuvi_measurements", "tags": {"mac": mac, "dataFormat": dataFormat}, "fields": fields} ] diff --git a/examples/post_to_influxdb_rx.py b/examples/post_to_influxdb_rx.py index 6881ae2..927806d 100644 --- a/examples/post_to_influxdb_rx.py +++ b/examples/post_to_influxdb_rx.py @@ -30,24 +30,20 @@ def write_to_influxdb(received_data): """ Convert data into RuuviCollector naming scheme and scale and write to InfluxDB. """ - dataFormat = received_data[1]["data_format"] if ("data_format" in received_data[1]) else None + dataFormat = received_data[1].get("data_format", None) fields = {} - fields["temperature"] = received_data[1]["temperature"] if ("temperature" in received_data[1]) else None - fields["humidity"] = received_data[1]["humidity"] if ("humidity" in received_data[1]) else None - fields["pressure"] = received_data[1]["pressure"] if ("pressure" in received_data[1]) else None - fields["accelerationX"] = received_data[1]["acceleration_x"] if ("acceleration_x" in received_data[1]) else None - fields["accelerationY"] = received_data[1]["acceleration_y"] if ("acceleration_y" in received_data[1]) else None - fields["accelerationZ"] = received_data[1]["acceleration_z"] if ("acceleration_z" in received_data[1]) else None + fields["temperature"] = received_data[1].get("temperature", None) + fields["humidity"] = received_data[1].get("humidity", None) + fields["pressure"] = received_data[1].get("pressure", None) + fields["accelerationX"] = received_data[1].get("acceleration_x", None) + fields["accelerationY"] = received_data[1].get("acceleration_y", None) + fields["accelerationZ"] = received_data[1].get("acceleration_z", None) fields["batteryVoltage"] = received_data[1]["battery"] / 1000.0 if ("battery" in received_data[1]) else None - fields["txPower"] = received_data[1]["tx_power"] if ("tx_power" in received_data[1]) else None - fields["movementCounter"] = ( - received_data[1]["movement_counter"] if ("movement_counter" in received_data[1]) else None - ) - fields["measurementSequenceNumber"] = ( - received_data[1]["measurement_sequence_number"] if ("measurement_sequence_number" in received_data[1]) else None - ) - fields["tagID"] = received_data[1]["tagID"] if ("tagID" in received_data[1]) else None - fields["rssi"] = received_data[1]["rssi"] if ("rssi" in received_data[1]) else None + fields["txPower"] = received_data[1].get("tx_power", None) + fields["movementCounter"] = received_data[1].get("movement_counter", None) + fields["measurementSequenceNumber"] = received_data[1].get("measurement_sequence_number", None) + fields["tagID"] = received_data[1].get("tagID", None) + fields["rssi"] = received_data[1].get("rssi", None) json_body = [ { "measurement": "ruuvi_measurements", diff --git a/examples/post_to_mqtt.py b/examples/post_to_mqtt.py index 92b7e46..ecae37e 100644 --- a/examples/post_to_mqtt.py +++ b/examples/post_to_mqtt.py @@ -53,7 +53,6 @@ # let's trap ctrl-c, SIGINT and come down nicely -# pylint: disable=unused-argument,redefined-outer-name def signal_handler(signal, frame): print("\nterminating gracefully.") client.disconnect() @@ -64,9 +63,8 @@ def signal_handler(signal, frame): # The callback for when the client receives a CONNACK response from the MQTT server. -# pylint: disable=unused-argument,redefined-outer-name def on_connect(client, userdata, flags, rc): - print(f"Connected to MQTT broker with result code {str(rc)}") + print(f"Connected to MQTT broker with result code {rc!s}") # Subscribing in on_connect() means that if we lose the connection and # reconnect then subscriptions will be renewed. diff --git a/examples/print_to_screen.py b/examples/print_to_screen.py index 16d2796..058ffdd 100644 --- a/examples/print_to_screen.py +++ b/examples/print_to_screen.py @@ -10,8 +10,6 @@ Pressure: 689 """ -# pylint: disable=duplicate-code - import os from datetime import datetime diff --git a/examples/print_to_screen_ruuvitag_class.py b/examples/print_to_screen_ruuvitag_class.py index 5605acc..33e5b50 100644 --- a/examples/print_to_screen_ruuvitag_class.py +++ b/examples/print_to_screen_ruuvitag_class.py @@ -14,8 +14,6 @@ process and eventually process creation may fail. """ -# pylint: disable=duplicate-code - import os import time from datetime import datetime diff --git a/examples/reactive_extensions.py b/examples/reactive_extensions.py index 1e83ca0..fb2c679 100644 --- a/examples/reactive_extensions.py +++ b/examples/reactive_extensions.py @@ -19,7 +19,7 @@ # Print only last updated every 10 seconds for F4:A5:74:89:16:57 ruuvi_rx.get_subject().pipe(ops.filter(lambda x: x[0] == "F4:A5:74:89:16:57"), ops.sample(interval_in_s)).subscribe( lambda data: print(data) -) # pylint: disable=unnecessary-lambda +) # Print only last updated every 10 seconds for every foud sensor ruuvi_rx.get_subject().pipe(ops.group_by(lambda x: x[0])).subscribe( @@ -29,9 +29,7 @@ # Print all from the last 10 seconds for F4:A5:74:89:16:57 ruuvi_rx.get_subject().pipe( ops.filter(lambda x: x[0] == "F4:A5:74:89:16:57"), ops.buffer_with_time(interval_in_s) -).subscribe( - lambda data: print(data) -) # pylint: disable=unnecessary-lambda +).subscribe(lambda data: print(data)) # Execute subscribe only once for F4:A5:74:89:16:57 # when temperature goes over 80 degrees diff --git a/examples/send_updated_async.py b/examples/send_updated_async.py index 284fc77..46b8017 100644 --- a/examples/send_updated_async.py +++ b/examples/send_updated_async.py @@ -31,7 +31,7 @@ async def send_post(session, update_data): async with session.post( f"{server_url}/sensordata", data=json.dumps(update_data), headers={"content-type": "application/json"} ) as response: - response = await response.read() + response = await response.read() # noqa: PLW2901 async def send_put(session, update_data): async with session.put( @@ -39,7 +39,7 @@ async def send_put(session, update_data): data=json.dumps(update_data), headers={"content-type": "application/json"}, ) as response: - response = await response.read() + response = await response.read() # noqa: PLW2901 async with ClientSession() as session: while True: diff --git a/pyproject.toml b/pyproject.toml index 1d9a779..ece58e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,11 +37,8 @@ dependencies = [ dev = [ "pytest", "pytest-asyncio", - "flake8-pyproject", - "pylint", - "mypy", - "isort", - "black" + "ruff", + "mypy" ] [project.urls] @@ -53,35 +50,35 @@ changelog = "https://github.com/ttu/ruuvitag-sensor/blob/master/CHANGELOG.md" [tool.pytest.ini_options] testpaths = "tests/" -[tool.flake8] -exclude = ".venv, .git, .eggs, __pycache__, build, dist" -max-line-length = 120 -ignore = "E402, E203" -show-source = true -max-complexity = 12 +[tool.ruff] +exclude = [".venv", ".git", ".eggs", "__pycache__", "build", "dist"] +line-length = 120 -[tool.pylint.messages_control] -max-line-length = 120 -disable = [ - "import-error", - "missing-docstring", - "invalid-name", - "bare-except", - "broad-except", - "broad-exception-raised", - "fixme", - "dangerous-default-value", - "too-few-public-methods", - "useless-object-inheritance" +[tool.ruff.lint] +select = [ + "A", # flake8-builtins + "C90", # maccabe + "E", # pycodestyle errors + "F", # pyflakes + "G", # flake8-logging-format + "I", # isort + "PERF", # perflint + "PIE", # flake8-pie + "PL", # pylint + "PTH", # flake8-use-pathlib + "Q", # flake8-quotes + "RUF", # ruff-specific rules + "SIM", # flake8-simplify + "W", # pycodestyle warnings ] +ignore = [ + "PLR2004", # Magic value used in comparison + "RUF006", # Store a reference to the return value of asyncio.create_task +] + +[tool.ruff.lint.mccabe] +max-complexity = 12 [tool.mypy] python_version = 3.9 ignore_missing_imports = true - -[tool.isort] -line_length = 120 -ensure_newline_before_comments = true - -[tool.black] -line-length = 120 diff --git a/ruuvitag_sensor/__init__.py b/ruuvitag_sensor/__init__.py index 7a2d189..2c02b64 100644 --- a/ruuvitag_sensor/__init__.py +++ b/ruuvitag_sensor/__init__.py @@ -1,3 +1,3 @@ import importlib.metadata -__version__ = importlib.metadata.version(__package__ or __name__) # pylint: disable=no-member +__version__ = importlib.metadata.version(__package__ or __name__) diff --git a/ruuvitag_sensor/adapters/__init__.py b/ruuvitag_sensor/adapters/__init__.py index e110c8a..56905e0 100644 --- a/ruuvitag_sensor/adapters/__init__.py +++ b/ruuvitag_sensor/adapters/__init__.py @@ -4,8 +4,6 @@ from ruuvitag_sensor.ruuvi_types import MacAndRawData, RawData -# pylint: disable=import-outside-toplevel, cyclic-import, too-many-return-statements - def get_ble_adapter(): forced_ble_adapter = os.environ.get("RUUVI_BLE_ADAPTER", "").lower() @@ -98,5 +96,5 @@ async def get_data(blacklist: List[str] = [], bt_device: str = "") -> AsyncGener # if False: yield is a mypy fix for # error: Return type "AsyncGenerator[Tuple[str, str], None]" of "get_data" incompatible with return type # "Coroutine[Any, Any, AsyncGenerator[Tuple[str, str], None]]" in supertype "BleCommunicationAsync" - if False: # pylint: disable=unreachable,using-constant-test + if False: yield 0 diff --git a/ruuvitag_sensor/adapters/bleak_ble.py b/ruuvitag_sensor/adapters/bleak_ble.py index bc0a34a..a82e9bd 100644 --- a/ruuvitag_sensor/adapters/bleak_ble.py +++ b/ruuvitag_sensor/adapters/bleak_ble.py @@ -21,7 +21,6 @@ def _get_scanner(detection_callback: AdvertisementDataCallback, bt_device: str = scanning_mode = "passive" if sys.platform.startswith("win") else "active" if "bleak_dev" in os.environ.get("RUUVI_BLE_ADAPTER", "").lower(): - # pylint: disable=import-outside-toplevel from ruuvitag_sensor.adapters.development.dev_bleak_scanner import DevBleakScanner return DevBleakScanner(detection_callback, scanning_mode) diff --git a/ruuvitag_sensor/adapters/bleson.py b/ruuvitag_sensor/adapters/bleson.py index 3254c7f..b08a889 100644 --- a/ruuvitag_sensor/adapters/bleson.py +++ b/ruuvitag_sensor/adapters/bleson.py @@ -13,8 +13,6 @@ log = logging.getLogger(__name__) -# pylint: disable=duplicate-code - class BleCommunicationBleson(BleCommunication): """Bluetooth LE communication with Bleson""" @@ -76,11 +74,8 @@ def start(bt_device=""): device (string): BLE device (default 0) """ - if not bt_device: - bt_device = 0 - else: - # Old communication used hci0 etc. - bt_device = bt_device.replace("hci", "") + # Old communication used hci0 etc. + bt_device = 0 if not bt_device else bt_device.replace("hci", "") log.info("Start receiving broadcasts (device %s)", bt_device) diff --git a/ruuvitag_sensor/adapters/development/dev_bleak_scanner.py b/ruuvitag_sensor/adapters/development/dev_bleak_scanner.py index aae4e68..66f2121 100644 --- a/ruuvitag_sensor/adapters/development/dev_bleak_scanner.py +++ b/ruuvitag_sensor/adapters/development/dev_bleak_scanner.py @@ -33,11 +33,9 @@ def __init__(self, callback, _): async def start(self): self.running = True asyncio.create_task(self.run()) - return None async def stop(self): self.running = False - return None async def run(self): while self.running: diff --git a/ruuvitag_sensor/adapters/nix_hci.py b/ruuvitag_sensor/adapters/nix_hci.py index c6cd500..4f01e90 100644 --- a/ruuvitag_sensor/adapters/nix_hci.py +++ b/ruuvitag_sensor/adapters/nix_hci.py @@ -10,8 +10,6 @@ log = logging.getLogger(__name__) -# pylint: disable=duplicate-code - class BleCommunicationNix(BleCommunication): """Bluetooth LE communication for Linux""" @@ -24,12 +22,12 @@ def start(bt_device=""): """ # import ptyprocess here so as long as all implementations are in # the same file, all will work - import ptyprocess # pylint: disable=import-outside-toplevel + import ptyprocess if not bt_device: bt_device = "hci0" - is_root = os.getuid() == 0 # pylint: disable=no-member + is_root = os.getuid() == 0 log.info("Start receiving broadcasts (device %s)", bt_device) DEVNULL = subprocess.DEVNULL @@ -95,9 +93,8 @@ def get_lines(hcidump): data = line[2:].replace(" ", "") elif line.startswith("< "): data = None - else: - if data: - data += line.replace(" ", "") + elif data: + data += line.replace(" ", "") except KeyboardInterrupt: return except Exception as ex: @@ -112,7 +109,7 @@ def get_data(blacklist: List[str] = [], bt_device: str = "") -> Generator[MacAnd log.debug("Parsing line %s", line) try: # Make sure we're in upper case - line = line.upper() + line = line.upper() # noqa: PLW2901 # We're interested in LE meta events, sent by Ruuvitags. # Those start with "043E", followed by a length byte. diff --git a/ruuvitag_sensor/adapters/nix_hci_file.py b/ruuvitag_sensor/adapters/nix_hci_file.py index 2e97c10..6934185 100644 --- a/ruuvitag_sensor/adapters/nix_hci_file.py +++ b/ruuvitag_sensor/adapters/nix_hci_file.py @@ -1,5 +1,7 @@ import logging +import Path + from ruuvitag_sensor.adapters.nix_hci import BleCommunicationNix log = logging.getLogger(__name__) @@ -19,7 +21,7 @@ def start(bt_device=""): This is interpreted as a file to open """ log.info("Start reading from file %s", bt_device) - handle = open(bt_device, "rb") # pylint: disable=consider-using-with + handle = Path.open(bt_device, "rb") return (None, handle) diff --git a/ruuvitag_sensor/data_formats.py b/ruuvitag_sensor/data_formats.py index 14cb82e..c5e5119 100644 --- a/ruuvitag_sensor/data_formats.py +++ b/ruuvitag_sensor/data_formats.py @@ -35,9 +35,8 @@ class DataFormats: RuuviTag broadcasted raw data handling for each data format """ - # pylint: disable=too-many-return-statements @staticmethod - def convert_data(raw: str) -> DataFormatAndRawSensorData: + def convert_data(raw: str) -> DataFormatAndRawSensorData: # noqa: PLR0911 """ Validate that data is from RuuviTag and get correct data part. @@ -118,7 +117,7 @@ def convert_data(raw: str) -> DataFormatAndRawSensorData: return (None, None) @staticmethod - def _parse_raw(raw: str, data_format: int) -> str: # pylint: disable=unused-argument + def _parse_raw(raw: str, data_format: int) -> str: return raw @staticmethod diff --git a/ruuvitag_sensor/ruuvi.py b/ruuvitag_sensor/ruuvi.py index 8eb332f..cc2b8c1 100644 --- a/ruuvitag_sensor/ruuvi.py +++ b/ruuvitag_sensor/ruuvi.py @@ -211,7 +211,7 @@ async def get_data_async(macs: List[str] = [], bt_device: str = "") -> AsyncGene data = RuuviTagSensor._parse_data(ble_data, mac_blacklist, macs) # Check MAC whitelist if advertised MAC available - if ble_data[0] and macs and not ble_data[0] in macs: + if ble_data[0] and macs and ble_data[0] not in macs: log.debug("MAC not whitelisted: %s", ble_data[0]) continue @@ -294,7 +294,7 @@ def _get_ruuvitag_data( data_iter.close() break # Check MAC whitelist if advertised MAC available - if ble_data[0] and macs and not ble_data[0] in macs: + if ble_data[0] and macs and ble_data[0] not in macs: log.debug("MAC not whitelisted: %s", ble_data[0]) continue @@ -331,7 +331,9 @@ def _parse_data( mac_to_send = ( mac if mac - else parse_mac(data_format, decoded["mac"]) if "mac" in decoded and decoded["mac"] is not None else "" + else parse_mac(data_format, decoded["mac"]) + if "mac" in decoded and decoded["mac"] is not None + else "" ) # Check whitelist using MAC from decoded data if advertised MAC is not available diff --git a/tests/test_data_formats.py b/tests/test_data_formats.py index 0dacfc7..45df6a4 100644 --- a/tests/test_data_formats.py +++ b/tests/test_data_formats.py @@ -1,7 +1,5 @@ from ruuvitag_sensor.data_formats import DataFormats -# pylint: disable=protected-access - class TestDataFormats: def test_convert_data_valid_data(self): diff --git a/tests/test_ruuvitag_sensor.py b/tests/test_ruuvitag_sensor.py index ee0cf1b..1b5ac32 100644 --- a/tests/test_ruuvitag_sensor.py +++ b/tests/test_ruuvitag_sensor.py @@ -6,8 +6,6 @@ from ruuvitag_sensor.ruuvi import RuuviTagSensor from ruuvitag_sensor.ruuvitag import RuuviTag -# pylint: disable=unused-argument - @patch("ruuvitag_sensor.ruuvi.ble", BleCommunicationDummy()) class TestRuuviTagSensor: diff --git a/tests/test_ruuvitag_sensor_async.py b/tests/test_ruuvitag_sensor_async.py index 3db78fd..ee92364 100644 --- a/tests/test_ruuvitag_sensor_async.py +++ b/tests/test_ruuvitag_sensor_async.py @@ -8,8 +8,6 @@ from ruuvitag_sensor.ruuvi import RuuviTagSensor from ruuvitag_sensor.ruuvitag import RuuviTagAsync -# pylint: disable=unused-argument - @pytest.mark.asyncio class TestRuuviTagSensorAsync: @@ -33,21 +31,17 @@ async def _get_data(self, blacklist=[], bt_device="") -> Tuple[str, str]: @patch("ruuvitag_sensor.ruuvi.ble", BleCommunicationAsyncDummy()) @patch("ruuvitag_sensor.adapters.dummy.BleCommunicationAsyncDummy.get_data", _get_data) async def test_get_data_async(self): - data = [] gener = RuuviTagSensor.get_data_async() - async for received in gener: - data.append(received) + data = [received async for received in gener] assert len(data) == 4 @patch("ruuvitag_sensor.ruuvi.ble", BleCommunicationAsyncDummy()) @patch("ruuvitag_sensor.adapters.dummy.BleCommunicationAsyncDummy.get_data", _get_data) async def test_get_data_async_with_macs(self): - data = [] macs = ["EB:A5:D1:02:CE:68", "EC:4D:A7:95:08:6B"] gener = RuuviTagSensor.get_data_async(macs) - async for received in gener: - data.append(received) + data = [received async for received in gener] assert len(data) == 2 diff --git a/verification.py b/verification.py index ce9a339..4133f31 100644 --- a/verification.py +++ b/verification.py @@ -83,7 +83,7 @@ def wait_for_finish(run_flag, name): # print_header("RuuviTagSensor.get_data_for_sensors with macs") -data = RuuviTagSensor.get_data_for_sensors(list(data.keys())[0], search_duration_sec=15) +data = RuuviTagSensor.get_data_for_sensors(next(iter(data.keys())), search_duration_sec=15) print(data) if not data: @@ -97,7 +97,7 @@ def wait_for_finish(run_flag, name): # print_header("RuuviTag.update") -tag = RuuviTag(list(data.keys())[0]) +tag = RuuviTag(next(iter(data.keys()))) tag.update() print(tag.state) @@ -147,7 +147,6 @@ def hadle_rx(found_data): ruuvi_rx.get_subject().subscribe(hadle_rx) -# pylint: disable=protected-access wait_for_finish(ruuvi_rx._run_flag, "ruuvi_rx.subscribe") diff --git a/verification_async.py b/verification_async.py index 8ef86f6..32413a2 100644 --- a/verification_async.py +++ b/verification_async.py @@ -83,7 +83,7 @@ async def test_get_data_for_sensors_async() -> list[str]: raise Exception("FAILED") print("OK") - return list(data.keys())[0] + return next(iter(data.keys())) async def test_get_data_for_sensors_async_with_macs(mac: list[str]): @@ -149,7 +149,6 @@ def handle_rx(found_data): ruuvi_rx.get_subject().subscribe(handle_rx) - # pylint: disable=protected-access await wait_for_finish(ruuvi_rx._run_flag, "ruuvi_rx.subscribe")