chore: konfiguracja ruff i poprawki wskazane przez lintera

Ruff jako linter i formatter, konfiguracja w pyproject.toml:

- reguły E, W, F, I, N, UP, B, C4, SIM, TID, BLE, RUF
- extend-immutable-calls dla fastapi.Depends, File i Query - wstrzykiwanie
  zależności opiera się na wywołaniach w wartościach domyślnych argumentów,
  więc bugbear B008 zgłaszał tam 66 fałszywych trafień
- zakaz importów względnych, aby ścieżki w pakiecie czytało się jednoznacznie
- katalog frontend wyłączony z analizy

Poprawki, które wskazał linter:

- Awaitable, Callable i AsyncIterator importowane z collections.abc zamiast
  z typing
- zbędny argument trybu w open(path, "r")
- contextlib.suppress zamiast try/except/pass przy anulowaniu zadania
- zdjęte dwie niepotrzebne dyrektywy noqa
- pięć linii przekraczających 100 znaków zawiniętych

Całość przepuszczona przez ruff format. Do .gitignore dochodzi .ruff_cache/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
lgooseandClaude Bot committed 2026-08-21 01:25:40 +02:00
1 parent ccfb0d2e34
commit 2a961e1a8a
12 files changed
+80 -30

No files matched your search

+3
View File
@@ -5,3 +5,6 @@ downloads/
.DS_Store
config/
uploads/
# Cache narzędzi deweloperskich
.ruff_cache/
+48
View File
@@ -0,0 +1,48 @@
[project]
name = "zyrarbot"
version = "2.0.0"
description = "Bot muzyczny Discord z panelem webowym"
requires-python = ">=3.13"
dynamic = ["dependencies"]
[tool.setuptools.dynamic]
dependencies = { file = ["requirements.txt"] }
[tool.setuptools.packages.find]
include = ["zyrarbot*"]
[tool.ruff]
target-version = "py313"
line-length = 100
extend-exclude = ["frontend"]
[tool.ruff.lint]
select = [
"E", # pycodestyle - błędy
"W", # pycodestyle - ostrzeżenia
"F", # pyflakes
"I", # isort - kolejność importów
"N", # pep8-naming
"UP", # pyupgrade
"B", # bugbear
"C4", # comprehensions
"SIM", # uproszczenia
"TID", # zakaz importów względnych
"BLE", # przechwytywanie ogólnych wyjątków
"RUF",
]
[tool.ruff.lint.flake8-bugbear]
# Wstrzykiwanie zależności w FastAPI opiera się na wywołaniach w wartościach
# domyślnych argumentów - to idiom biblioteki, nie pomyłka.
extend-immutable-calls = ["fastapi.Depends", "fastapi.File", "fastapi.Query"]
[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"
[tool.ruff.lint.isort]
known-first-party = ["zyrarbot", "tests"]
[tool.ruff.lint.per-file-ignores]
# Testy sięgają po prywatne pola atrap i celowo łamią kilka konwencji.
"tests/*" = ["SLF001"]
+3 -1
View File
@@ -21,7 +21,9 @@ class TrackFactory:
self._probe = probe or MediaProbe()
self._ytdl = yt_dlp.YoutubeDL(YtdlOptions.EXTRACTOR)
async def resolve(self, query: str, *, loop: asyncio.AbstractEventLoop | None = None) -> YTDLSource:
async def resolve(
self, query: str, *, loop: asyncio.AbstractEventLoop | None = None
) -> YTDLSource:
"""Rozpoznaje, czy zapytanie wskazuje wgrany plik, czy zasób sieciowy."""
if not query.startswith("http") and self._uploads.exists(query):
return await self.from_file(self._uploads.resolve(query))
+1 -3
View File
@@ -36,9 +36,7 @@ class ZyrarBot(commands.Bot):
if self.player is None:
raise RuntimeError("MusicPlayer musi zostać przypisany przed startem bota")
await self.add_cog(
MusicCog(self, self.player, self.tracks, self.uploads, self.settings)
)
await self.add_cog(MusicCog(self, self.player, self.tracks, self.uploads, self.settings))
if self.settings.guild_id:
guild = discord.Object(id=self.settings.guild_id)
+12 -12
View File
@@ -74,10 +74,10 @@ class MusicCog(commands.Cog, name="MusicBot"):
return False
return True
async def _enqueue_and_reply(self, interaction: discord.Interaction, track, *, prefix: str = "") -> None:
result = await self.player.enqueue(
interaction.guild.id, track, channel=interaction.channel
)
async def _enqueue_and_reply(
self, interaction: discord.Interaction, track, *, prefix: str = ""
) -> None:
result = await self.player.enqueue(interaction.guild.id, track, channel=interaction.channel)
title = discord.utils.escape_markdown(track.title or "Nieznany tytuł")
action = "Dodano do kolejki" if result["status"] == "queued" else "Teraz odtwarzane"
await interaction.followup.send(f"{action}{prefix}: {title}")
@@ -161,7 +161,9 @@ class MusicCog(commands.Cog, name="MusicBot"):
for entry in matches[:AUTOCOMPLETE_LIMIT]
]
@app_commands.command(name="history", description="Pokaż historię ostatnio odtwarzanych utworów")
@app_commands.command(
name="history", description="Pokaż historię ostatnio odtwarzanych utworów"
)
async def show_history(self, interaction: discord.Interaction) -> None:
entries = self.player.history(interaction.guild.id)
if not entries:
@@ -263,9 +265,7 @@ class MusicCog(commands.Cog, name="MusicBot"):
voice_client.stop()
self.player.clear(guild_id)
await self.player.trigger_state_change()
await interaction.response.send_message(
"Zatrzymano odtwarzanie i wyczyszczono kolejkę."
)
await interaction.response.send_message("Zatrzymano odtwarzanie i wyczyszczono kolejkę.")
@app_commands.command(name="volume", description="Zmień głośność odtwarzacza")
@app_commands.describe(volume="Poziom głośności (1-100)")
@@ -299,7 +299,9 @@ class MusicCog(commands.Cog, name="MusicBot"):
@app_commands.command(name="leave", description="Odłącz bota od kanału głosowego")
async def leave(self, interaction: discord.Interaction) -> None:
if interaction.guild.voice_client is None:
await interaction.response.send_message("Nie jestem na kanale głosowym.", ephemeral=True)
await interaction.response.send_message(
"Nie jestem na kanale głosowym.", ephemeral=True
)
return
await interaction.guild.voice_client.disconnect()
self.player.clear(interaction.guild.id)
@@ -327,9 +329,7 @@ class MusicCog(commands.Cog, name="MusicBot"):
await self.player.trigger_state_change()
description = (
"włączone (utwory po zakończeniu wracają na koniec kolejki)"
if enabled
else "wyłączone"
"włączone (utwory po zakończeniu wracają na koniec kolejki)" if enabled else "wyłączone"
)
await interaction.response.send_message(f"🔁 Zapętlanie całej kolejki: **{description}**.")
+3 -4
View File
@@ -2,7 +2,8 @@ from __future__ import annotations
import asyncio
import logging
from typing import Any, Awaitable, Callable
from collections.abc import Awaitable, Callable
from typing import Any
import discord
from discord.ext import commands
@@ -266,9 +267,7 @@ class MusicPlayer:
def _schedule_next(self, guild_id: int, error: Exception | None) -> None:
if error:
logger.error("Odtwarzanie zakończone błędem na serwerze %s: %s", guild_id, error)
asyncio.run_coroutine_threadsafe(
self.play_next(guild_id, advance=True), self._bot.loop
)
asyncio.run_coroutine_threadsafe(self.play_next(guild_id, advance=True), self._bot.loop)
def _record_history(self, guild_id: int, track: YTDLSource) -> None:
if not track.title:
+3 -1
View File
@@ -22,7 +22,9 @@ class TrackEmbedFactory:
embed.set_footer(text=f"Pozycja w kolejce: {position} / {total}")
return embed
def _base(self, track: YTDLSource, color: discord.Color, *, prefix: str = "🎵") -> discord.Embed:
def _base(
self, track: YTDLSource, color: discord.Color, *, prefix: str = "🎵"
) -> discord.Embed:
title = discord.utils.escape_markdown(track.title or "Nieznany tytuł")
url = track.display_url
icon = "📁" if track.is_local else prefix
+1 -1
View File
@@ -22,7 +22,7 @@ class JsonFileStore:
if not os.path.exists(self._path):
return {}
try:
with open(self._path, "r", encoding="utf-8") as handle:
with open(self._path, encoding="utf-8") as handle:
data = json.load(handle)
except (OSError, ValueError) as error:
logger.error("Nie udało się odczytać %s: %s", self._path, error)
+1 -1
View File
@@ -2,8 +2,8 @@ from __future__ import annotations
import logging
import os
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import AsyncIterator
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
+2 -2
View File
@@ -47,7 +47,7 @@ async def play(
try:
track = await tracks.resolve(request.query)
result = await player.enqueue(guild.id, track, from_web=True)
except Exception as error: # noqa: BLE001 - treść błędu trafia do panelu
except Exception as error:
logger.error("Nie udało się dodać utworu z panelu: %s", error)
raise HTTPException(status_code=500, detail=str(error)) from error
@@ -73,7 +73,7 @@ async def play_file(
uploads.touch(request.filename)
track = await tracks.from_file(uploads.resolve(request.filename))
result = await player.enqueue(guild.id, track, from_web=True)
except Exception as error: # noqa: BLE001
except Exception as error:
logger.error("Nie udało się odtworzyć pliku z panelu: %s", error)
raise HTTPException(status_code=500, detail=str(error)) from error
+2 -3
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import asyncio
import contextlib
import logging
from zyrarbot.web.connection_manager import ConnectionManager
@@ -35,10 +36,8 @@ class StatusBroadcaster:
if self._task is None:
return
self._task.cancel()
try:
with contextlib.suppress(asyncio.CancelledError):
await self._task
except asyncio.CancelledError:
pass
self._task = None
async def _loop(self) -> None:
+1 -2
View File
@@ -75,8 +75,7 @@ class StatusService:
str(notification_channel.id) if notification_channel else None
),
"history": [
entry.to_dict()
for entry in self._player.history(guild.id)[:HISTORY_PAYLOAD_LIMIT]
entry.to_dict() for entry in self._player.history(guild.id)[:HISTORY_PAYLOAD_LIMIT]
],
}