Skip to content

Commit

Permalink
Turn on pyright in precommit and checks
Browse files Browse the repository at this point in the history
  • Loading branch information
jkeljo committed Sep 3, 2023
1 parent 474cd87 commit aabdbb1
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 30 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ jobs:
python -m pip install --upgrade pip
pip install poetry
poetry install
echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
- name: Build the wheel
run: poetry build -f wheel
- name: Typecheck with pyright
uses: jakebailey/pyright-action@v1
- name: Run tests
run: poetry run pytest
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ repos:
# https://pre-commit.com/#top_level-default_language_version
language_version: python3.11
args: [--experimental-string-processing]

- repo: local
hooks:
- id: pyright
name: pyright
language: system
entry: poetry run pyright --verbose
files: .*\.py$
pass_filenames: false
2 changes: 1 addition & 1 deletion dump_packets.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async def spy(host: str) -> None:


async def redirect(
gem: str, original=Tuple[str, int], redirect=Tuple[str, int]
gem: str, original: Tuple[str, int], redirect: Tuple[str, int]
) -> None:
async with aiohttp.ClientSession() as session:
async with Monitors() as monitors:
Expand Down
2 changes: 1 addition & 1 deletion greeneye/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .monitor import Monitors # noqa: F401
from .monitor import Monitors # noqa: F401 # type: ignore
8 changes: 4 additions & 4 deletions greeneye/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,20 +266,20 @@ def unpack(format: str | bytes) -> Tuple[Any, ...]:
ecm_parser=None,
)

_SET_CT_TYPE = ApiCall[(int, int), None](
_SET_CT_TYPE = ApiCall[Tuple[int, int], None](
gem_formatter=lambda args: f"^^^C{args[0]:02}TYP{args[1]}",
gem_parser=None,
ecm_formatter=None,
ecm_parser=None,
)
_SET_CT_RANGE = ApiCall[(int, int), None](
_SET_CT_RANGE = ApiCall[Tuple[int, int], None](
gem_formatter=lambda args: f"^^^C{args[0]:02}RNG{args[1]}",
gem_parser=None,
ecm_formatter=None,
ecm_parser=None,
)
_SET_CT_TYPE_AND_RANGE = ApiCall[(int, int, int), None](
gem_formatter=None,
_SET_CT_TYPE_AND_RANGE = ApiCall[Tuple[int, int, int], None](
gem_formatter=None, # type: ignore
gem_parser=None,
ecm_formatter=lambda args: [
b"\xfc",
Expand Down
49 changes: 35 additions & 14 deletions greeneye/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@
from datetime import datetime, timedelta
from enum import Enum
from types import TracebackType
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union
from typing import (
Awaitable,
Callable,
Dict,
List,
Optional,
ParamSpec,
Tuple,
Type,
TypeVar,
Union,
)

import aiohttp
from siobrultech_protocols.gem import api
Expand Down Expand Up @@ -551,7 +562,7 @@ async def _configure_from_settings(
# Voltage sensor was created up front

# Now update settings if needed and trigger listeners
coroutines = []
coroutines: list[Awaitable[None]] = []
for temperature_sensor in self.temperature_sensors:
coroutines.append(temperature_sensor.handle_settings(settings))
for channel in self.channels:
Expand Down Expand Up @@ -587,12 +598,10 @@ async def _configure_from_packet(self, packet: Packet) -> None:

self._configured = True
LOG.info(f"Configured {self.serial_number} from first packet.")
coroutines = []
for listener in self._listeners:
coroutines.append(_ensure_coroutine(listener)())
await asyncio.gather(*coroutines)
await _invoke_listeners(self._listeners)

async def set_packet_send_interval(self, seconds: int) -> None:
assert self._control
await self._control.set_packet_send_interval(seconds)

def set_packet_interval(self, seconds: int) -> None:
Expand Down Expand Up @@ -626,9 +635,11 @@ async def handle_packet(self, packet: Packet) -> None:


async def _invoke_listeners(listeners: List[Listener]) -> None:
coroutines = [_ensure_coroutine(listener)() for listener in listeners]
coroutines: list[Awaitable[None]] = [
_ensure_coroutine(listener)() for listener in listeners
]
if len(coroutines) > 0:
await asyncio.gather(*coroutines) # type: ignore
await asyncio.gather(*coroutines)


ServerListener = Callable[[PacketProtocolMessage], Awaitable[None]]
Expand All @@ -640,7 +651,9 @@ class MonitorProtocolProcessor:
packet."""

def __init__(self, listener: ServerListener, send_packet_delay: bool) -> None:
self._consumer_task = asyncio.ensure_future(self._consumer())
self._consumer_task: asyncio.Task[None] | None = asyncio.ensure_future(
self._consumer()
)
LOG.debug("Packet processor started")
self._listener = listener
self._queue: asyncio.Queue[PacketProtocolMessage] = asyncio.Queue()
Expand Down Expand Up @@ -783,7 +796,7 @@ async def _handle_message(self, message: PacketProtocolMessage) -> None:
else:
if isinstance(message, ConnectionLostMessage):
for monitor in self._protocol_to_monitors.pop(protocol_id):
await monitor._set_protocol(None)
await monitor._set_protocol(None) # type: ignore
elif isinstance(message, ConnectionMadeMessage):
self._protocol_to_monitors[protocol_id] = set()

Expand All @@ -804,7 +817,9 @@ async def _set_monitor_protocol(
) -> None:
protocol_id = id(protocol)
self._protocol_to_monitors[protocol_id].add(monitor)
await monitor._set_protocol(protocol, api_timeout=self._api_timeout)
await monitor._set_protocol( # type: ignore
protocol, api_timeout=self._api_timeout
)

async def _notify_new_monitor(self, monitor: Monitor) -> None:
listeners = [
Expand All @@ -814,12 +829,18 @@ async def _notify_new_monitor(self, monitor: Monitor) -> None:
await asyncio.gather(*listeners) # type: ignore


def _ensure_coroutine(listener):
P = ParamSpec("P")
R = TypeVar("R")


def _ensure_coroutine(
listener: Union[Callable[P, Awaitable[R]], Callable[P, R]]
) -> Callable[P, Awaitable[R]]:
if inspect.iscoroutinefunction(listener):
return listener
else:

async def async_listener(*args):
listener(*args)
async def async_listener(*args: P.args, **kwargs: P.kwargs) -> R:
return listener(*args, **kwargs) # type: ignore

return async_listener
50 changes: 49 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,11 @@ siobrultech-protocols = "0.12"

[tool.poetry.group.test.dependencies]
ruff = "*"
pyright = "*"
pytest = "*"
pytest-socket = "*"

[tool.pyright]
include = ["greeneye", "tests", "dump_packets.py"]
pythonVersion = "3.10"
typeCheckingMode = "strict"
8 changes: 5 additions & 3 deletions tests/packet_test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import os
from io import StringIO

from siobrultech_protocols.gem.packets import Packet

greeneye_dir = os.path.dirname(os.path.abspath(__file__))
greeneye_data_dir = os.path.join(greeneye_dir, "data")

Expand Down Expand Up @@ -779,20 +781,20 @@
}


def read_packets(packet_file_names):
def read_packets(packet_file_names: list[str]) -> bytes:
result = bytearray()
for packet_file_name in packet_file_names:
result.extend(read_packet(packet_file_name))

return bytes(result)


def read_packet(packet_file_name):
def read_packet(packet_file_name: str) -> bytes:
with open(os.path.join(greeneye_data_dir, packet_file_name), "rb") as data_file:
return data_file.read()


def assert_packet(packet_file_name, parsed_packet):
def assert_packet(packet_file_name: str, parsed_packet: Packet):
expected_packet = PACKETS[packet_file_name]

expected = StringIO()
Expand Down
11 changes: 8 additions & 3 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

from siobrultech_protocols.gem.packets import PacketFormatType

from greeneye.api import TemperatureUnit, _parse_all_settings
from greeneye.api import _parse_all_settings # type: ignore
from greeneye.api import GemSettings, TemperatureUnit


class TestAllSettings(unittest.TestCase):
def setUp(self):
self.settings = _parse_all_settings(
settings = _parse_all_settings(
"ALL\r\n00,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,00,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,D3,D3,D3,D2,D3,D3,D4,D3,D3,D3,D3,D3,D3,D3,D3,D2,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D2,D3,D3,D2,90,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,44,44,44,43,44,44,44,44,44,44,44,44,44,44,44,24,44,44,44,44,44,44,44,44,89,03,08,05,00,00,1E,00,87,00,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,18,26,A6,80,C7,18,25,81,87,C6,18,25,A4,40,27,F9,86,81,BF,78,6E,25,87,4E,84,88,4E,85,89,A6,8A,B0,83,C7,04,EB,C7,05,1B,A6,00,B2,82,C7,04,EA,C7,05,1A,55,82,D6,00,DC,B7,86,CD,04,B0,AF,01,8B,89,55,00,00,A6,20,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,AF,FF,35,84,65,DD,00,00,00,00,00,00,00,0F,00,00,00,00,00,01,01,00,00,01,07,FF,01,50,17,70,20,7F,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00\r\n"
)
assert settings
self.settings: GemSettings = settings

def testPacketFormat(self):
self.assertEqual(PacketFormatType.BIN32_NET, self.settings.packet_format)
Expand All @@ -31,7 +34,9 @@ def testNotNetMetering(self):
self.assertFalse(self.settings.channel_net_metering[0])

def testBadPacketFormat(self):
self.settings = _parse_all_settings(
settings = _parse_all_settings(
"ALL\r\n00,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,00,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,40,D3,D3,D3,D2,D3,D3,D4,D3,D3,D3,D3,D3,D3,D3,D3,D2,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D2,D3,D3,D2,90,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,D3,44,44,44,43,44,44,44,44,44,44,44,44,44,44,44,24,44,44,44,44,44,44,44,44,89,03,0A,05,00,00,1E,00,87,00,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,01,18,26,A6,80,C7,18,25,81,87,C6,18,25,A4,40,27,F9,86,81,BF,78,6E,25,87,4E,84,88,4E,85,89,A6,8A,B0,83,C7,04,EB,C7,05,1B,A6,00,B2,82,C7,04,EA,C7,05,1A,55,82,D6,00,DC,B7,86,CD,04,B0,AF,01,8B,89,55,00,00,A6,20,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,AF,FF,35,84,65,DD,00,00,00,00,00,00,00,0F,00,00,00,00,00,01,01,00,00,01,07,FF,01,50,17,70,20,7F,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00\r\n"
)
assert settings
self.settings = settings
self.assertIsNone(self.settings.packet_format)
10 changes: 7 additions & 3 deletions tests/test_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import socket
import unittest
from datetime import timedelta
from typing import Optional, cast
from typing import Callable, Optional, cast

import pytest
from siobrultech_protocols.gem.packets import PacketFormatType
Expand Down Expand Up @@ -85,7 +85,9 @@ async def testMonitorConfiguredProperlyWhenClientIgnoresAPI(self):
functools.partial(ApiUnawareClient, packet="BIN32-NET.bin")
)

async def assertMonitorConfiguredProperlyWithClient(self, client):
async def assertMonitorConfiguredProperlyWithClient(
self, client: Callable[[], asyncio.Protocol]
):
loop = asyncio.get_event_loop()
async with Monitors(
send_packet_delay=False, api_timeout=timedelta(seconds=0)
Expand Down Expand Up @@ -129,7 +131,9 @@ async def testMonitorConfiguredProperlyWhenClientIgnoresAPI(self) -> None:
functools.partial(ApiUnawareClient, packet="ECM-1240.bin")
)

async def assertMonitorConfiguredProperlyWithClient(self, client):
async def assertMonitorConfiguredProperlyWithClient(
self, client: Callable[[], asyncio.Protocol]
):
loop = asyncio.get_event_loop()
async with Monitors(
send_packet_delay=False, api_timeout=timedelta(seconds=0)
Expand Down

0 comments on commit aabdbb1

Please sign in to comment.