diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0aaa014..173aa01 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -50,7 +50,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9688042..5578e28 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,10 +9,10 @@ jobs: name: Build with Poetry and Publish to PyPI runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: - python-version: "3.10" + python-version: "3.12" - name: Install and configure Poetry run: | pip install poetry diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12e87f4..469131e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,14 @@ name: Tests on: [push, pull_request] jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: chartboost/ruff-action@v1 test: runs-on: ubuntu-latest + needs: ruff strategy: matrix: python-version: @@ -13,9 +19,9 @@ jobs: - "3.11" - "3.12" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install and configure Poetry @@ -26,4 +32,4 @@ jobs: run: poetry install - name: Pytest run: | - poetry run pytest --isort --black --pylama + poetry run pytest diff --git a/examples/movie_xmas.py b/examples/movie_xmas.py index 717e976..4ef0e82 100644 --- a/examples/movie_xmas.py +++ b/examples/movie_xmas.py @@ -13,7 +13,7 @@ def generate_xmas_frame(n: int) -> TwinklyFrame: """Generate a very merry frame""" res = [] - for i in range(0, n): + for _ in range(n): if random.random() > 0.5: res.append(RED) else: @@ -53,7 +53,7 @@ def main() -> None: movie = [] - for n in range(0, args.count): + for _ in range(args.count): frame = [v for sublist in generate_xmas_frame(args.leds) for v in sublist] movie.extend(frame) diff --git a/examples/realtime_xmas.py b/examples/realtime_xmas.py index 9c9eecd..592554d 100644 --- a/examples/realtime_xmas.py +++ b/examples/realtime_xmas.py @@ -15,7 +15,7 @@ def generate_xmas_frame(n: int) -> TwinklyFrame: """Generate a very merry frame""" res = [] - for i in range(0, n): + for _ in range(n): if random.random() > 0.5: res.append(RED) else: diff --git a/pyproject.toml b/pyproject.toml index a4f15dc..e0989ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,12 +20,9 @@ aiohttp = "^3.8.5" [tool.poetry.group.dev.dependencies] pytest = "^7.4.2" -pylama = "^8.4.1" -black = "^22.10.0" -isort = "^5.10.1" -pytest-black = "^0.3.12" -pytest-isort = "^3.1.0" aiounittest = "^1.4.2" +ruff = "^0.4.8" +pytest-ruff = "^0.3.2" [build-system] requires = ["poetry-core>=1.0.0"] @@ -34,12 +31,19 @@ build-backend = "poetry.core.masonry.api" [tool.isort] profile = "black" -[tool.pylama] -max_line_length = 132 - -[tool.pylama.linter.pycodestyle] -ignore = "E203,W503" - -[tool.pylama.linter.mccabe] -complexity = 40 -ignore = "C901" +[tool.ruff.lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # pyupgrade + "UP", + # flake8-bugbear + "B", + # flake8-simplify + "SIM", + # isort + "I", +] +ignore = ["E501"] diff --git a/ttls/client.py b/ttls/client.py index f721d9e..408f16d 100644 --- a/ttls/client.py +++ b/ttls/client.py @@ -25,6 +25,7 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ + from __future__ import annotations import base64 @@ -33,7 +34,7 @@ import socket import time from itertools import cycle, islice -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Optional, Tuple from aiohttp import ClientResponseError, ClientSession, ClientTimeout from aiohttp.web_exceptions import HTTPUnauthorized @@ -42,7 +43,7 @@ _LOGGER = logging.getLogger(__name__) -TwinklyFrame = List[TwinklyColourTuple] +TwinklyFrame = list[TwinklyColourTuple] TwinklyResult = Optional[dict] @@ -100,12 +101,12 @@ DEFAULT_TIMEOUT = 3 -class Twinkly(object): +class Twinkly: def __init__( self, host: str, - session: Optional[ClientSession] = None, - timeout: Optional[int] = None, + session: ClientSession | None = None, + timeout: int | None = None, ): self.host = host self._timeout = ClientTimeout(total=timeout or DEFAULT_TIMEOUT) @@ -116,11 +117,11 @@ def __init__( self._session = None self._shared_session = False self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - self._headers: Dict[str, str] = {} + self._headers: dict[str, str] = {} self._rt_port = 7777 self._expires = None self._token = None - self._details: Dict[str, Union[str, int]] = {} + self._details: dict[str, str | int] = {} self._default_mode = "movie" @property @@ -231,7 +232,8 @@ async def _handle_authorized( retry_num += 1 _LOGGER.debug( - f"Invalid token for request. Refreshing token and attempting retry {retry_num} of {max_retries}." + "Invalid token for request. " + + f"Refreshing token and attempting retry {retry_num} of {max_retries}." ) await self.refresh_token() return await request_method( @@ -290,7 +292,7 @@ async def get_firmware_version(self) -> Any: async def get_details(self) -> Any: return self._valid_response(await self._get("gestalt")) - async def is_on(self) -> Optional[bool]: + async def is_on(self) -> bool | None: mode = await self.get_mode() if mode is None: return None @@ -369,16 +371,14 @@ async def upload_movie(self, movie: bytes) -> Any: async def set_static_colour( self, - colour: Union[ - TwinklyColour, - TwinklyColourTuple, - List[TwinklyColour], - List[TwinklyColourTuple], - ], + colour: TwinklyColour + | TwinklyColourTuple + | list[TwinklyColour] + | list[TwinklyColourTuple], ) -> None: if not self._details: await self.interview() - if isinstance(colour, List): + if isinstance(colour, list): colour = colour[0] if isinstance(colour, Tuple): colour = TwinklyColour.from_twinkly_tuple(colour) @@ -390,18 +390,16 @@ async def set_static_colour( async def set_cycle_colours( self, - colour: Union[ - TwinklyColour, - TwinklyColourTuple, - List[TwinklyColour], - List[TwinklyColourTuple], - ], + colour: TwinklyColour + | TwinklyColourTuple + | list[TwinklyColour] + | list[TwinklyColourTuple], ) -> None: if isinstance(colour, TwinklyColour): sequence = [colour.as_twinkly_tuple()] elif isinstance(colour, Tuple): sequence = [colour] - elif isinstance(colour, List): + elif isinstance(colour, list): if isinstance(colour[0], TwinklyColour): sequence = [c.as_twinkly_tuple() for c in colour] else: diff --git a/ttls/colours.py b/ttls/colours.py index b683614..ad4a932 100644 --- a/ttls/colours.py +++ b/ttls/colours.py @@ -28,8 +28,7 @@ def as_tuple(self) -> ColourTuple: return (self.red, self.green, self.blue) def __iter__(self): - for i in self.as_tuple(): - yield i + yield from self.as_tuple() def as_dict(self) -> ColourDict: """Convert TwinklyColour to a dict wth color names used by set-led functions."""