fix: Lint fixes of services
All checks were successful
Backend CI / test (push) Successful in 3m59s

This commit is contained in:
JSC
2025-08-01 01:27:47 +02:00
parent 95ccb76233
commit a10111793c
12 changed files with 237 additions and 160 deletions

View File

@@ -400,7 +400,6 @@ async def play_sound_with_vlc(
await credit_service.validate_and_reserve_credits(
current_user.id,
CreditActionType.VLC_PLAY_SOUND,
{"sound_id": sound_id, "sound_name": sound.name},
)
except InsufficientCreditsError as e:
raise HTTPException(
@@ -418,8 +417,8 @@ async def play_sound_with_vlc(
await credit_service.deduct_credits(
current_user.id,
CreditActionType.VLC_PLAY_SOUND,
success,
{"sound_id": sound_id, "sound_name": sound.name},
success=success,
metadata={"sound_id": sound_id, "sound_name": sound.name},
)
if not success:

View File

@@ -76,14 +76,12 @@ class CreditService:
self,
user_id: int,
action_type: CreditActionType,
metadata: dict[str, Any] | None = None,
) -> tuple[User, CreditAction]:
"""Validate user has sufficient credits and optionally reserve them.
Args:
user_id: The user ID
action_type: The type of action
metadata: Optional metadata to store with transaction
Returns:
Tuple of (user, credit_action)
@@ -118,6 +116,7 @@ class CreditService:
self,
user_id: int,
action_type: CreditActionType,
*,
success: bool = True,
metadata: dict[str, Any] | None = None,
) -> CreditTransaction:
@@ -139,18 +138,26 @@ class CreditService:
"""
action = get_credit_action(action_type)
# Only deduct if action requires success and was successful, or doesn't require success
should_deduct = (action.requires_success and success) or not action.requires_success
# Only deduct if action requires success and was successful,
# or doesn't require success
should_deduct = (
action.requires_success and success
) or not action.requires_success
if not should_deduct:
logger.info(
"Skipping credit deduction for user %s: action %s failed and requires success",
"Skipping credit deduction for user %s: "
"action %s failed and requires success",
user_id,
action_type.value,
)
# Still create a transaction record for auditing
return await self._create_transaction_record(
user_id, action, 0, success, metadata,
user_id,
action,
0,
success=success,
metadata=metadata,
)
session = self.db_session_factory()
@@ -204,14 +211,19 @@ class CreditService:
"action_type": action_type.value,
"success": success,
}
await socket_manager.send_to_user(str(user_id), "user_credits_changed", event_data)
await socket_manager.send_to_user(
str(user_id),
"user_credits_changed",
event_data,
)
logger.info("Emitted user_credits_changed event for user %s", user_id)
except Exception:
logger.exception(
"Failed to emit user_credits_changed event for user %s", user_id,
"Failed to emit user_credits_changed event for user %s",
user_id,
)
return transaction
else:
return transaction
except Exception:
await session.rollback()
@@ -292,14 +304,19 @@ class CreditService:
"description": description,
"success": True,
}
await socket_manager.send_to_user(str(user_id), "user_credits_changed", event_data)
await socket_manager.send_to_user(
str(user_id),
"user_credits_changed",
event_data,
)
logger.info("Emitted user_credits_changed event for user %s", user_id)
except Exception:
logger.exception(
"Failed to emit user_credits_changed event for user %s", user_id,
"Failed to emit user_credits_changed event for user %s",
user_id,
)
return transaction
else:
return transaction
except Exception:
await session.rollback()
@@ -312,6 +329,7 @@ class CreditService:
user_id: int,
action: CreditAction,
amount: int,
*,
success: bool,
metadata: dict[str, Any] | None = None,
) -> CreditTransaction:
@@ -342,19 +360,22 @@ class CreditService:
amount=amount,
balance_before=user.credits,
balance_after=user.credits,
description=f"{action.description} (failed)" if not success else action.description,
description=(
f"{action.description} (failed)"
if not success
else action.description
),
success=success,
metadata_json=json.dumps(metadata) if metadata else None,
)
session.add(transaction)
await session.commit()
return transaction
except Exception:
await session.rollback()
raise
else:
return transaction
finally:
await session.close()

View File

@@ -75,7 +75,10 @@ class ExtractionService:
extraction = await self.extraction_repo.create(extraction_data)
logger.info("Created extraction with ID: %d", extraction.id)
except Exception:
logger.exception("Failed to create extraction for URL: %s", url)
raise
else:
return {
"id": extraction.id or 0, # Should never be None for created extraction
"url": extraction.url,
@@ -87,10 +90,6 @@ class ExtractionService:
"sound_id": extraction.sound_id,
}
except Exception:
logger.exception("Failed to create extraction for URL: %s", url)
raise
async def _detect_service_info(self, url: str) -> dict[str, str | None] | None:
"""Detect service information from URL using yt-dlp."""
try:
@@ -126,10 +125,12 @@ class ExtractionService:
"""Process an extraction job."""
extraction = await self.extraction_repo.get_by_id(extraction_id)
if not extraction:
raise ValueError(f"Extraction {extraction_id} not found")
msg = f"Extraction {extraction_id} not found"
raise ValueError(msg)
if extraction.status != "pending":
raise ValueError(f"Extraction {extraction_id} is not pending")
msg = f"Extraction {extraction_id} is not pending"
raise ValueError(msg)
# Store all needed values early to avoid session detachment issues
user_id = extraction.user_id
@@ -150,7 +151,8 @@ class ExtractionService:
service_info = await self._detect_service_info(extraction_url)
if not service_info:
raise ValueError("Unable to detect service information from URL")
msg = "Unable to detect service information from URL"
raise ValueError(msg)
# Check if extraction already exists for this service
existing = await self.extraction_repo.get_by_service_and_id(
@@ -222,7 +224,12 @@ class ExtractionService:
)
logger.info("Successfully processed extraction %d", extraction_id)
except Exception as e:
error_msg = str(e)
logger.exception(
"Failed to process extraction %d: %s", extraction_id, error_msg,
)
else:
return {
"id": extraction_id,
"url": extraction_url,
@@ -234,12 +241,6 @@ class ExtractionService:
"sound_id": sound_id,
}
except Exception as e:
error_msg = str(e)
logger.exception(
"Failed to process extraction %d: %s", extraction_id, error_msg,
)
# Update extraction with error
await self.extraction_repo.update(
extraction,
@@ -313,7 +314,8 @@ class ExtractionService:
)
if not audio_files:
raise RuntimeError("No audio file was created during extraction")
msg = "No audio file was created during extraction"
raise RuntimeError(msg)
audio_file = audio_files[0]
thumbnail_file = thumbnail_files[0] if thumbnail_files else None
@@ -324,11 +326,12 @@ class ExtractionService:
thumbnail_file or "None",
)
return audio_file, thumbnail_file
except Exception as e:
logger.exception("yt-dlp extraction failed for %s", extraction_url)
raise RuntimeError(f"Audio extraction failed: {e}") from e
error_msg = f"Audio extraction failed: {e}"
raise RuntimeError(error_msg) from e
else:
return audio_file, thumbnail_file
async def _move_files_to_final_location(
self,
@@ -450,8 +453,8 @@ class ExtractionService:
else:
logger.info("Successfully normalized sound %d", sound_id)
except Exception as e:
logger.exception("Error normalizing sound %d: %s", sound_id, e)
except Exception:
logger.exception("Error normalizing sound %d", sound_id)
# Don't fail the extraction if normalization fails
async def _add_to_main_playlist(self, sound_id: int, user_id: int) -> None:

View File

@@ -1,6 +1,7 @@
"""Background extraction processor for handling extraction queue."""
import asyncio
import contextlib
from sqlmodel.ext.asyncio.session import AsyncSession
@@ -51,10 +52,8 @@ class ExtractionProcessor:
"Extraction processor did not stop gracefully, cancelling...",
)
self.processor_task.cancel()
try:
with contextlib.suppress(asyncio.CancelledError):
await self.processor_task
except asyncio.CancelledError:
pass
logger.info("Extraction processor stopped")
@@ -84,8 +83,8 @@ class ExtractionProcessor:
except TimeoutError:
continue # Continue processing
except Exception as e:
logger.exception("Error in extraction queue processor: %s", e)
except Exception:
logger.exception("Error in extraction queue processor")
# Wait a bit before retrying to avoid tight error loops
try:
await asyncio.wait_for(self.shutdown_event.wait(), timeout=10.0)
@@ -156,8 +155,8 @@ class ExtractionProcessor:
result["status"],
)
except Exception as e:
logger.exception("Error processing extraction %d: %s", extraction_id, e)
except Exception:
logger.exception("Error processing extraction %d", extraction_id)
def _on_extraction_completed(self, extraction_id: int, task: asyncio.Task) -> None:
"""Handle completion of an extraction task."""

View File

@@ -68,15 +68,19 @@ class PlayerState:
"duration": self.current_sound_duration,
"index": self.current_sound_index,
"current_sound": self._serialize_sound(self.current_sound),
"playlist": {
"id": self.playlist_id,
"name": self.playlist_name,
"length": self.playlist_length,
"duration": self.playlist_duration,
"sounds": [
self._serialize_sound(sound) for sound in self.playlist_sounds
],
} if self.playlist_id else None,
"playlist": (
{
"id": self.playlist_id,
"name": self.playlist_name,
"length": self.playlist_length,
"duration": self.playlist_duration,
"sounds": [
self._serialize_sound(sound) for sound in self.playlist_sounds
],
}
if self.playlist_id
else None
),
}
def _serialize_sound(self, sound: Sound | None) -> dict[str, Any] | None:
@@ -103,6 +107,14 @@ class PlayerService:
self.db_session_factory = db_session_factory
self.state = PlayerState()
self._vlc_instance = vlc.Instance()
if self._vlc_instance is None:
msg = (
"VLC instance could not be created. "
"Ensure VLC is installed and accessible."
)
raise RuntimeError(msg)
self._player = self._vlc_instance.media_player_new()
self._is_running = False
self._position_thread: threading.Thread | None = None
@@ -125,7 +137,8 @@ class PlayerService:
# Start position tracking thread
self._position_thread = threading.Thread(
target=self._position_tracker, daemon=True,
target=self._position_tracker,
daemon=True,
)
self._position_thread.start()
@@ -155,9 +168,9 @@ class PlayerService:
"""Play audio at specified index or current position."""
# Check if we're resuming from pause
is_resuming = (
index is None and
self.state.status == PlayerStatus.PAUSED and
self.state.current_sound is not None
index is None
and self.state.status == PlayerStatus.PAUSED
and self.state.current_sound is not None
)
if is_resuming:
@@ -179,7 +192,14 @@ class PlayerService:
}
await self._broadcast_state()
logger.info("Resumed playing sound: %s", self.state.current_sound.name)
logger.info(
"Resumed playing sound: %s",
(
self.state.current_sound.name
if self.state.current_sound
else "Unknown"
),
)
else:
logger.error("Failed to resume playback: VLC error code %s", result)
return
@@ -204,6 +224,10 @@ class PlayerService:
return
# Load and play media (new track)
if self._vlc_instance is None:
logger.error("VLC instance is not initialized. Cannot play media.")
return
media = self._vlc_instance.media_new(str(sound_path))
self._player.set_media(media)
@@ -354,7 +378,9 @@ class PlayerService:
and previous_playlist_id != current_playlist.id
):
await self._handle_playlist_id_changed(
previous_playlist_id, current_playlist.id, sounds,
previous_playlist_id,
current_playlist.id,
sounds,
)
elif previous_current_sound_id:
await self._handle_same_playlist_track_check(
@@ -432,7 +458,9 @@ class PlayerService:
self._clear_current_track()
def _update_playlist_state(
self, current_playlist: Playlist, sounds: list[Sound],
self,
current_playlist: Playlist,
sounds: list[Sound],
) -> None:
"""Update basic playlist state information."""
self.state.playlist_id = current_playlist.id
@@ -464,7 +492,6 @@ class PlayerService:
"""Get current player state."""
return self.state.to_dict()
def _get_next_index(self, current_index: int) -> int | None:
"""Get next track index based on current mode."""
if not self.state.playlist_sounds:
@@ -497,11 +524,7 @@ class PlayerService:
prev_index = current_index - 1
if prev_index < 0:
return (
playlist_length - 1
if self.state.mode == PlayerMode.LOOP
else None
)
return playlist_length - 1 if self.state.mode == PlayerMode.LOOP else None
return prev_index
def _position_tracker(self) -> None:
@@ -535,10 +558,7 @@ class PlayerService:
def _update_play_time(self) -> None:
"""Update play time tracking for current sound."""
if (
not self.state.current_sound_id
or self.state.status != PlayerStatus.PLAYING
):
if not self.state.current_sound_id or self.state.status != PlayerStatus.PLAYING:
return
sound_id = self.state.current_sound_id
@@ -577,10 +597,8 @@ class PlayerService:
sound_id,
tracking["total_time"],
self.state.current_sound_duration,
(
tracking["total_time"]
/ self.state.current_sound_duration
) * 100,
(tracking["total_time"] / self.state.current_sound_duration)
* 100,
)
self._schedule_async_task(self._record_play_count(sound_id))
@@ -596,7 +614,8 @@ class PlayerService:
if sound:
old_count = sound.play_count
await sound_repo.update(
sound, {"play_count": sound.play_count + 1},
sound,
{"play_count": sound.play_count + 1},
)
logger.info(
"Updated sound %s play_count: %s -> %s",

View File

@@ -69,6 +69,7 @@ class PlaylistService:
name: str,
description: str | None = None,
genre: str | None = None,
*,
is_main: bool = False,
is_current: bool = False,
is_deletable: bool = True,
@@ -104,6 +105,7 @@ class PlaylistService:
self,
playlist_id: int,
user_id: int,
*,
name: str | None = None,
description: str | None = None,
genre: str | None = None,
@@ -179,7 +181,11 @@ class PlaylistService:
return await self.playlist_repo.get_playlist_sounds(playlist_id)
async def add_sound_to_playlist(
self, playlist_id: int, sound_id: int, user_id: int, position: int | None = None,
self,
playlist_id: int,
sound_id: int,
user_id: int,
position: int | None = None,
) -> None:
"""Add a sound to a playlist."""
# Verify playlist exists
@@ -202,11 +208,17 @@ class PlaylistService:
await self.playlist_repo.add_sound_to_playlist(playlist_id, sound_id, position)
logger.info(
"Added sound %s to playlist %s for user %s", sound_id, playlist_id, user_id,
"Added sound %s to playlist %s for user %s",
sound_id,
playlist_id,
user_id,
)
async def remove_sound_from_playlist(
self, playlist_id: int, sound_id: int, user_id: int,
self,
playlist_id: int,
sound_id: int,
user_id: int,
) -> None:
"""Remove a sound from a playlist."""
# Verify playlist exists
@@ -228,7 +240,10 @@ class PlaylistService:
)
async def reorder_playlist_sounds(
self, playlist_id: int, user_id: int, sound_positions: list[tuple[int, int]],
self,
playlist_id: int,
user_id: int,
sound_positions: list[tuple[int, int]],
) -> None:
"""Reorder sounds in a playlist."""
# Verify playlist exists
@@ -262,7 +277,8 @@ class PlaylistService:
await self._unset_current_playlist(user_id)
await self._set_main_as_current(user_id)
logger.info(
"Unset current playlist and set main as current for user %s", user_id,
"Unset current playlist and set main as current for user %s",
user_id,
)
async def get_playlist_stats(self, playlist_id: int) -> dict[str, Any]:
@@ -286,11 +302,13 @@ class PlaylistService:
main_playlist = await self.get_main_playlist()
if main_playlist.id is None:
raise ValueError("Main playlist has no ID")
msg = "Main playlist has no ID, cannot add sound"
raise ValueError(msg)
# Check if sound is already in main playlist
if not await self.playlist_repo.is_sound_in_playlist(
main_playlist.id, sound_id,
main_playlist.id,
sound_id,
):
await self.playlist_repo.add_sound_to_playlist(main_playlist.id, sound_id)
logger.info(

View File

@@ -13,7 +13,8 @@ logger = logging.getLogger(__name__)
class SocketManager:
"""Manages WebSocket connections and user rooms."""
def __init__(self):
def __init__(self) -> None:
"""Initialize the SocketManager with a Socket.IO server."""
self.sio = socketio.AsyncServer(
cors_allowed_origins=["http://localhost:8001"],
logger=True,
@@ -27,20 +28,20 @@ class SocketManager:
self._setup_handlers()
def _setup_handlers(self):
def _setup_handlers(self) -> None:
"""Set up socket event handlers."""
@self.sio.event
async def connect(sid, environ, auth=None):
async def connect(sid: str, environ: dict) -> None:
"""Handle client connection."""
logger.info(f"Client {sid} attempting to connect")
logger.info("Client %s attempting to connect", sid)
# Extract access token from cookies
cookie_header = environ.get("HTTP_COOKIE", "")
access_token = extract_access_token_from_cookies(cookie_header)
if not access_token:
logger.warning(f"Client {sid} connecting without access token")
logger.warning("Client %s connecting without access token", sid)
await self.sio.disconnect(sid)
return
@@ -50,13 +51,13 @@ class SocketManager:
user_id = payload.get("sub")
if not user_id:
logger.warning(f"Client {sid} token missing user ID")
logger.warning("Client %s token missing user ID", sid)
await self.sio.disconnect(sid)
return
logger.info(f"User {user_id} connected with socket {sid}")
except Exception as e:
logger.warning(f"Client {sid} invalid token: {e}")
logger.info("User %s connected with socket %s", user_id, sid)
except Exception:
logger.exception("Client %s invalid token", sid)
await self.sio.disconnect(sid)
return
@@ -70,7 +71,7 @@ class SocketManager:
# Update room tracking
self.user_rooms[user_id] = room_id
logger.info(f"User {user_id} joined room {room_id}")
logger.info("User %s joined room %s", user_id, room_id)
# Send welcome message to user
await self.sio.emit(
@@ -84,33 +85,33 @@ class SocketManager:
)
@self.sio.event
async def disconnect(sid):
async def disconnect(sid: str) -> None:
"""Handle client disconnection."""
user_id = self.socket_users.get(sid)
if user_id:
logger.info(f"User {user_id} disconnected (socket {sid})")
logger.info("User %s disconnected (socket %s)", user_id, sid)
# Clean up mappings
del self.socket_users[sid]
if user_id in self.user_rooms:
del self.user_rooms[user_id]
else:
logger.info(f"Unknown client {sid} disconnected")
logger.info("Unknown client %s disconnected", sid)
async def send_to_user(self, user_id: str, event: str, data: dict):
async def send_to_user(self, user_id: str, event: str, data: dict) -> bool:
"""Send a message to a specific user's room."""
room_id = self.user_rooms.get(user_id)
if room_id:
await self.sio.emit(event, data, room=room_id)
logger.debug(f"Sent {event} to user {user_id} in room {room_id}")
logger.debug("Sent %s to user %s in room %s", event, user_id, room_id)
return True
logger.warning(f"User {user_id} not found in any room")
logger.warning("User %s not found in any room", user_id)
return False
async def broadcast_to_all(self, event: str, data: dict):
async def broadcast_to_all(self, event: str, data: dict) -> None:
"""Broadcast a message to all connected users."""
await self.sio.emit(event, data)
logger.info(f"Broadcasted {event} to all users")
logger.info("Broadcasted %s to all users", event)
def get_connected_users(self) -> list:
"""Get list of currently connected user IDs."""

View File

@@ -153,7 +153,9 @@ class SoundNormalizerService:
"""Normalize audio using two-pass loudnorm for better quality."""
try:
logger.info(
"Starting two-pass normalization: %s -> %s", input_path, output_path,
"Starting two-pass normalization: %s -> %s",
input_path,
output_path,
)
# First pass: analyze
@@ -177,7 +179,7 @@ class SoundNormalizerService:
result = ffmpeg.run(stream, capture_stderr=True, quiet=True)
analysis_output = result[1].decode("utf-8")
except ffmpeg.Error as e:
logger.error(
logger.exception(
"FFmpeg first pass failed for %s. Stdout: %s, Stderr: %s",
input_path,
e.stdout.decode() if e.stdout else "None",
@@ -193,9 +195,11 @@ class SoundNormalizerService:
json_match = re.search(r'\{[^{}]*"input_i"[^{}]*\}', analysis_output)
if not json_match:
logger.error(
"Could not find JSON in loudnorm output: %s", analysis_output,
"Could not find JSON in loudnorm output: %s",
analysis_output,
)
raise ValueError("Could not extract loudnorm analysis data")
msg = "Could not find JSON in loudnorm output"
raise ValueError(msg)
logger.debug("Found JSON match: %s", json_match.group())
analysis_data = json.loads(json_match.group())
@@ -211,7 +215,10 @@ class SoundNormalizerService:
]:
if str(analysis_data.get(key, "")).lower() in invalid_values:
logger.warning(
"Invalid analysis value for %s: %s. Falling back to one-pass normalization.",
(
"Invalid analysis value for %s: %s. "
"Falling back to one-pass normalization."
),
key,
analysis_data.get(key),
)
@@ -252,7 +259,7 @@ class SoundNormalizerService:
ffmpeg.run(stream, quiet=True, overwrite_output=True)
logger.info("Two-pass normalization completed: %s", output_path)
except ffmpeg.Error as e:
logger.error(
logger.exception(
"FFmpeg second pass failed for %s. Stdout: %s, Stderr: %s",
input_path,
e.stdout.decode() if e.stdout else "None",
@@ -267,12 +274,14 @@ class SoundNormalizerService:
async def normalize_sound(
self,
sound: Sound,
*,
force: bool = False,
one_pass: bool | None = None,
sound_data: dict | None = None,
) -> NormalizationInfo:
"""Normalize a single sound."""
# Use provided sound_data to avoid detached instance issues, or capture from sound
# Use provided sound_data to avoid detached instance issues,
# or capture from sound
if sound_data:
filename = sound_data["filename"]
sound_id = sound_data["id"]
@@ -391,6 +400,7 @@ class SoundNormalizerService:
async def normalize_all_sounds(
self,
*,
force: bool = False,
one_pass: bool | None = None,
) -> NormalizationResults:
@@ -409,7 +419,7 @@ class SoundNormalizerService:
if force:
# Get all sounds if forcing
sounds = []
for sound_type in self.type_directories.keys():
for sound_type in self.type_directories:
type_sounds = await self.sound_repo.get_by_type(sound_type)
sounds.extend(type_sounds)
else:
@@ -419,17 +429,16 @@ class SoundNormalizerService:
logger.info("Found %d sounds to process", len(sounds))
# Capture all sound data upfront to avoid session detachment issues
sound_data_list = []
for sound in sounds:
sound_data_list.append(
{
"id": sound.id,
"filename": sound.filename,
"type": sound.type,
"is_normalized": sound.is_normalized,
"name": sound.name,
},
)
sound_data_list = [
{
"id": sound.id,
"filename": sound.filename,
"type": sound.type,
"is_normalized": sound.is_normalized,
"name": sound.name,
}
for sound in sounds
]
# Process each sound using captured data
for i, sound in enumerate(sounds):
@@ -485,6 +494,7 @@ class SoundNormalizerService:
async def normalize_sounds_by_type(
self,
sound_type: str,
*,
force: bool = False,
one_pass: bool | None = None,
) -> NormalizationResults:
@@ -508,17 +518,16 @@ class SoundNormalizerService:
logger.info("Found %d %s sounds to process", len(sounds), sound_type)
# Capture all sound data upfront to avoid session detachment issues
sound_data_list = []
for sound in sounds:
sound_data_list.append(
{
"id": sound.id,
"filename": sound.filename,
"type": sound.type,
"is_normalized": sound.is_normalized,
"name": sound.name,
},
)
sound_data_list = [
{
"id": sound.id,
"filename": sound.filename,
"type": sound.type,
"is_normalized": sound.is_normalized,
"name": sound.name,
}
for sound in sounds
]
# Process each sound using captured data
for i, sound in enumerate(sounds):

View File

@@ -23,7 +23,8 @@ class VLCPlayerService:
"""Service for launching VLC instances via subprocess to play sounds."""
def __init__(
self, db_session_factory: Callable[[], AsyncSession] | None = None,
self,
db_session_factory: Callable[[], AsyncSession] | None = None,
) -> None:
"""Initialize the VLC player service."""
self.vlc_executable = self._find_vlc_executable()
@@ -52,7 +53,7 @@ class VLCPlayerService:
# For "vlc", try to find it in PATH
if path == "vlc":
result = subprocess.run(
["which", "vlc"],
["which", "vlc"], # noqa: S607
capture_output=True,
check=False,
text=True,
@@ -112,13 +113,19 @@ class VLCPlayerService:
# Record play count and emit event
if self.db_session_factory and sound.id:
asyncio.create_task(self._record_play_count(sound.id, sound.name))
return True
task = asyncio.create_task(
self._record_play_count(sound.id, sound.name),
)
# Store reference to prevent garbage collection
self._background_tasks = getattr(self, "_background_tasks", set())
self._background_tasks.add(task)
task.add_done_callback(self._background_tasks.discard)
except Exception:
logger.exception("Failed to launch VLC for sound %s", sound.name)
return False
else:
return True
async def stop_all_vlc_instances(self) -> dict[str, Any]:
"""Stop all running VLC processes by killing them.
@@ -287,7 +294,8 @@ class VLCPlayerService:
logger.info("Broadcasted sound_played event for sound %s", sound_id)
except Exception:
logger.exception(
"Failed to broadcast sound_played event for sound %s", sound_id,
"Failed to broadcast sound_played event for sound %s",
sound_id,
)
except Exception:
@@ -297,7 +305,6 @@ class VLCPlayerService:
await session.close()
# Global VLC player service instance
vlc_player_service: VLCPlayerService | None = None
@@ -310,3 +317,4 @@ def get_vlc_player_service(
if vlc_player_service is None:
vlc_player_service = VLCPlayerService(db_session_factory)
return vlc_player_service
return vlc_player_service

View File

@@ -70,7 +70,7 @@ def requires_credits(
# Validate credits before execution
await credit_service.validate_and_reserve_credits(
user_id, action_type, metadata,
user_id, action_type,
)
# Execute the function
@@ -86,7 +86,7 @@ def requires_credits(
finally:
# Deduct credits based on success
await credit_service.deduct_credits(
user_id, action_type, success, metadata,
user_id, action_type, success=success, metadata=metadata,
)
return wrapper # type: ignore[return-value]
@@ -173,7 +173,7 @@ class CreditManager:
async def __aenter__(self) -> "CreditManager":
"""Enter context manager - validate credits."""
await self.credit_service.validate_and_reserve_credits(
self.user_id, self.action_type, self.metadata,
self.user_id, self.action_type,
)
self.validated = True
return self
@@ -189,7 +189,7 @@ class CreditManager:
# If no exception occurred, consider it successful
success = exc_type is None and self.success
await self.credit_service.deduct_credits(
self.user_id, self.action_type, success, self.metadata,
self.user_id, self.action_type, success=success, metadata=self.metadata,
)
def mark_success(self) -> None: