Fixing everything, making the project structure ready for real code.
Some checks failed
Tests / Frontend Tests (TypeScript + Vue + Yarn Berry) (push) Failing after 7m49s
Tests / Backend Tests (Python 3.13 + uv) (push) Failing after 14m36s

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-10-23 12:58:32 -04:00
parent 17a8ab1a06
commit 2c8f424a81
13 changed files with 173 additions and 562 deletions

View File

@@ -1,14 +1,22 @@
"""Pytest configuration for automatic typeguard integration."""
from typeguard import install_import_hook
def pytest_configure(config):
def pytest_configure(config) -> None: # noqa: ARG001
"""Configure pytest to use typeguard automatically."""
# This enables typeguard for all functions with type hints
# without requiring @typechecked decorators
pass
# Install typeguard import hook to automatically check all functions
# with type hints in the backend package during test runs
install_import_hook("backend")
# Also check any other packages in src/
install_import_hook("src")
print("🛡️ Automatic typeguard hooks installed for test run")
def pytest_runtest_setup(item):
"""Set up typeguard for each test run."""
# Import hook is automatically enabled via --typeguard-packages
# The import hook is automatically enabled via pytest_configure
# All functions with type hints will be automatically checked
pass

View File

@@ -15,31 +15,6 @@ dev = [
"pytest-mock>=3.12.0"
]
[project]
dependencies = [
"fastapi>=0.104.0",
"uvicorn[standard]>=0.24.0",
"sqlalchemy>=2.0.0",
"pydantic>=2.5.0"
]
description = "Plex Playlist Management API"
name = "plex-playlist-backend"
requires-python = ">=3.12"
version = "0.1.0"
[project.optional-dependencies]
dev = [
"ruff>=0.6.0",
"pyright>=1.1.380",
"darglint>=1.8.1",
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.1.0",
"typeguard>=4.1.0",
"httpx>=0.25.0", # For testing async HTTP calls
"pytest-mock>=3.12.0"
]
[tool.coverage]
[tool.coverage.report]
@@ -78,8 +53,7 @@ addopts = [
"--cov=backend",
"--cov-report=term-missing:skip-covered",
"--cov-report=html",
"--cov-report=xml",
"--typeguard-packages=backend"
"--cov-report=xml"
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",

View File

@@ -1,10 +1,7 @@
"""
Plex Playlist Backend Package.
This package provides the FastAPI backend for playlist management
with automatic typeguard validation.
"""
from .main import PlaylistManager, app, calculate_playlist_duration, process_user_data
from .main import app
__all__ = ["app", "process_user_data", "calculate_playlist_duration", "PlaylistManager"]
__all__ = ["app"]

View File

@@ -1,8 +1,5 @@
"""
Hypermodern Python setup for automatic typeguard validation.
This approach enables runtime type checking without requiring decorators
or explicit validation calls in your application code.
Plex Playlist Backend API.
"""
from fastapi import FastAPI
@@ -10,13 +7,10 @@ from fastapi import FastAPI
# Create FastAPI application instance
app = FastAPI(
title="Plex Playlist Backend",
description="API for managing Plex playlists with automatic type validation",
description="API for managing Plex playlists",
version="0.1.0",
)
# No imports needed in application code - typeguard works automatically
# when enabled via pytest configuration
@app.get("/")
def read_root() -> dict[str, str]:
@@ -28,93 +22,3 @@ def read_root() -> dict[str, str]:
def health_check() -> dict[str, str]:
"""Health check endpoint."""
return {"status": "healthy"}
def process_user_data(user_id: int, name: str, email: str) -> dict[str, str | int]:
"""Process user data with automatic type validation.
This function has type hints, and when running under pytest with
--typeguard-packages, all calls will be automatically validated
without any decorators or explicit checks.
Args:
user_id: Unique identifier for the user
name: User's full name
email: User's email address
Returns:
Dictionary containing processed user information
"""
return {
"id": user_id,
"name": name.title(),
"email": email.lower(),
"display_name": f"{name} ({email})",
}
def calculate_playlist_duration(track_durations: list[float]) -> float:
"""Calculate total playlist duration.
Automatic type validation ensures track_durations is actually a list
of floats, without any explicit validation code.
Args:
track_durations: List of track durations in seconds
Returns:
Total duration in seconds
"""
return sum(track_durations)
class PlaylistManager:
"""Playlist management with automatic type validation."""
def __init__(self, name: str, max_size: int = 1000) -> None:
"""Initialize playlist manager.
Args:
name: Name of the playlist manager
max_size: Maximum number of tracks allowed
"""
self.name = name
self.max_size = max_size
self.tracks: list[str] = []
def add_track(self, track_id: str) -> bool:
"""Add a track to the playlist.
Args:
track_id: Unique identifier for the track
Returns:
True if track was added successfully
"""
if len(self.tracks) >= self.max_size:
return False
self.tracks.append(track_id)
return True
def get_track_count(self) -> int:
"""Get the number of tracks in the playlist.
Returns:
Number of tracks currently in the playlist
"""
return len(self.tracks)
# API endpoints using the classes and functions with automatic validation
@app.post("/api/users")
def create_user(user_id: int, name: str, email: str) -> dict[str, str | int]:
"""Create user endpoint with automatic type validation."""
return process_user_data(user_id, name, email)
@app.post("/api/playlists/{playlist_name}/duration")
def calculate_duration(track_durations: list[float]) -> dict[str, float]:
"""Calculate playlist duration endpoint."""
total_duration = calculate_playlist_duration(track_durations)
return {"total_duration": total_duration}

View File

@@ -1,99 +0,0 @@
"""Example tests for the Plex Playlist backend with automatic typeguard."""
import pytest
def add_numbers(a: int, b: int) -> int:
"""Add two numbers together.
Args:
a: First number to add
b: Second number to add
Returns:
The sum of a and b
"""
return a + b
def create_playlist_data(
name: str, description: str | None = None
) -> dict[str, str | None]:
"""Create playlist data structure.
Args:
name: Name of the playlist
description: Optional description
Returns:
Dictionary containing playlist data
"""
return {
"name": name,
"description": description,
"id": f"playlist_{name.lower().replace(' ', '_')}",
}
class TestMathFunctions:
"""Test mathematical functions with automatic type checking."""
def test_add_numbers_valid_types(self) -> None:
"""Test adding numbers with correct types."""
result = add_numbers(5, 3)
assert result == 8
assert isinstance(result, int)
def test_add_numbers_invalid_types(self) -> None:
"""Test that typeguard catches invalid types automatically."""
with pytest.raises(TypeError):
# Typeguard will automatically catch this type violation
add_numbers("5", 3) # type: ignore[arg-type]
def test_add_numbers_coverage_example(self) -> None:
"""Test different number combinations for coverage."""
assert add_numbers(0, 0) == 0
assert add_numbers(-1, 1) == 0
assert add_numbers(100, 200) == 300
class TestPlaylistFunctions:
"""Test playlist-related functions with automatic type checking."""
def test_create_playlist_data_with_description(self) -> None:
"""Test creating playlist data with description."""
result = create_playlist_data("My Playlist", "A great playlist")
expected = {
"name": "My Playlist",
"description": "A great playlist",
"id": "playlist_my_playlist",
}
assert result == expected
def test_create_playlist_data_without_description(self) -> None:
"""Test creating playlist data without description."""
result = create_playlist_data("Simple Playlist")
expected = {
"name": "Simple Playlist",
"description": None,
"id": "playlist_simple_playlist",
}
assert result == expected
def test_create_playlist_invalid_name_type(self) -> None:
"""Test that typeguard automatically catches invalid name type."""
with pytest.raises(TypeError):
# No decorator needed - typeguard import hook handles this
create_playlist_data(123, "description") # type: ignore[arg-type]
@pytest.mark.integration
class TestIntegrationExample:
"""Example integration tests with automatic type validation."""
def test_integration_placeholder(self) -> None:
"""Placeholder for real integration tests."""
# All functions called here will have automatic type checking
assert True

View File

@@ -1,86 +0,0 @@
"""
Tests demonstrating hypermodern typeguard usage.
No decorators needed - validation happens automatically.
"""
import pytest
from backend import PlaylistManager, calculate_playlist_duration, process_user_data
class TestHypermodernTypeguard:
"""Tests showing automatic type validation without decorators."""
def test_process_user_data_valid_types(self) -> None:
"""Test with correct types - should work normally."""
result = process_user_data(123, "John Doe", "JOHN@EXAMPLE.COM")
expected = {
"id": 123,
"name": "John Doe",
"email": "john@example.com",
"display_name": "John Doe (john@example.com)",
}
assert result == expected
def test_process_user_data_invalid_user_id(self) -> None:
"""Test with wrong user_id type - typeguard catches automatically."""
with pytest.raises(TypeError):
# This will fail because "123" is str, not int
process_user_data("123", "John", "john@example.com") # type: ignore[arg-type]
def test_process_user_data_invalid_name(self) -> None:
"""Test with wrong name type - typeguard catches automatically."""
with pytest.raises(TypeError):
# This will fail because 123 is int, not str
process_user_data(123, 123, "john@example.com") # type: ignore[arg-type]
def test_calculate_duration_valid(self) -> None:
"""Test duration calculation with valid types."""
durations = [3.5, 4.2, 2.8, 5.1]
total = calculate_playlist_duration(durations)
assert abs(total - 15.6) < 0.001 # Float comparison
def test_calculate_duration_invalid_list_type(self) -> None:
"""Test with wrong parameter type - not a list."""
with pytest.raises(TypeError):
# This will fail because "not a list" is str, not list[float]
calculate_playlist_duration("not a list") # type: ignore[arg-type]
def test_calculate_duration_invalid_element_types(self) -> None:
"""Test with wrong element types in list."""
with pytest.raises(TypeError):
# This will fail because list contains strings, not floats
calculate_playlist_duration(["3.5", "4.2"]) # type: ignore[list-item]
def test_playlist_manager_creation(self) -> None:
"""Test playlist manager creation with valid types."""
manager = PlaylistManager("My Playlist", 500)
assert manager.name == "My Playlist"
assert manager.max_size == 500
assert manager.get_track_count() == 0
def test_playlist_manager_invalid_name_type(self) -> None:
"""Test playlist manager creation with invalid name type."""
with pytest.raises(TypeError):
# This will fail because 123 is int, not str
PlaylistManager(123) # type: ignore[arg-type]
def test_playlist_manager_add_track_valid(self) -> None:
"""Test adding tracks with valid types."""
manager = PlaylistManager("Test Playlist", 2)
assert manager.add_track("track_1") is True
assert manager.add_track("track_2") is True
assert manager.add_track("track_3") is False # Exceeds max_size
assert manager.get_track_count() == 2
def test_playlist_manager_add_track_invalid_type(self) -> None:
"""Test adding track with invalid type."""
manager = PlaylistManager("Test Playlist")
with pytest.raises(TypeError):
# This will fail because 123 is int, not str
manager.add_track(123) # type: ignore[arg-type]