traxsource: helper _split_title_mix + _format_artists + _large_cover + eccezioni
This commit is contained in:
1 parent
a95912f78c
commit
e1e629f8a2
2 files changed
+95
No files matched your search
@@ -6,6 +6,7 @@ Spec: docs/superpowers/specs/2026-08-01-traxsource-charts-design.md.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
@@ -59,3 +60,47 @@ def list_genres() -> list:
|
|||||||
]
|
]
|
||||||
result.sort(key=lambda g: g["name"].casefold())
|
result.sort(key=lambda g: g["name"].casefold())
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class TraxsourceError(Exception):
|
||||||
|
"""Base per errori Traxsource."""
|
||||||
|
|
||||||
|
|
||||||
|
class TraxsourceUnreachableError(TraxsourceError):
|
||||||
|
"""Rete / 5xx dopo retry."""
|
||||||
|
|
||||||
|
|
||||||
|
class TraxsourceParseError(TraxsourceError):
|
||||||
|
"""HTML ricevuto ma non conforme allo schema atteso."""
|
||||||
|
|
||||||
|
|
||||||
|
_MIX_PAREN_RE = re.compile(r"^(.*)\s*\(([^()]+)\)\s*$")
|
||||||
|
_SIZE_RE = re.compile(r"/\d+x\d+/")
|
||||||
|
|
||||||
|
|
||||||
|
def _split_title_mix(full_title: str) -> tuple:
|
||||||
|
"""Estrae mix dalle parentesi finali. 'Foo (Extended Mix)' -> ('Foo', 'Extended Mix').
|
||||||
|
Se non ci sono parentesi finali, mix = ''."""
|
||||||
|
if not full_title:
|
||||||
|
return ("", "")
|
||||||
|
m = _MIX_PAREN_RE.match(full_title.strip())
|
||||||
|
if m:
|
||||||
|
return (m.group(1).strip(), m.group(2).strip())
|
||||||
|
return (full_title.strip(), "")
|
||||||
|
|
||||||
|
|
||||||
|
def _format_artists(names: list) -> str:
|
||||||
|
"""['A', 'B', 'C'] -> 'A, B & C'. Strips whitespace."""
|
||||||
|
clean = [n.strip() for n in names if n and n.strip()]
|
||||||
|
if not clean:
|
||||||
|
return ""
|
||||||
|
if len(clean) == 1:
|
||||||
|
return clean[0]
|
||||||
|
return ", ".join(clean[:-1]) + " & " + clean[-1]
|
||||||
|
|
||||||
|
|
||||||
|
def _large_cover(url: str) -> str:
|
||||||
|
"""Sostituisce /NxN/ nel path con /500x500/. Se pattern assente, ritorna invariato."""
|
||||||
|
if not url:
|
||||||
|
return ""
|
||||||
|
return _SIZE_RE.sub("/500x500/", url)
|
||||||
@@ -45,3 +45,53 @@ class TestTraxsourceTrack:
|
|||||||
)
|
)
|
||||||
assert t.image_url == ""
|
assert t.image_url == ""
|
||||||
assert t.cover_url_large == ""
|
assert t.cover_url_large == ""
|
||||||
|
|
||||||
|
|
||||||
|
class TestSplitTitleMix:
|
||||||
|
def test_with_parens(self):
|
||||||
|
assert traxsource._split_title_mix("Foo (Extended Mix)") == ("Foo", "Extended Mix")
|
||||||
|
|
||||||
|
def test_without_parens(self):
|
||||||
|
assert traxsource._split_title_mix("Foo") == ("Foo", "")
|
||||||
|
|
||||||
|
def test_multiple_parens_takes_last(self):
|
||||||
|
# es. "Foo (feat. Bar) (Original Mix)" -> mix = "Original Mix"
|
||||||
|
assert traxsource._split_title_mix("Foo (feat. Bar) (Original Mix)") == ("Foo (feat. Bar)", "Original Mix")
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
assert traxsource._split_title_mix("") == ("", "")
|
||||||
|
|
||||||
|
|
||||||
|
class TestFormatArtists:
|
||||||
|
def test_single(self):
|
||||||
|
assert traxsource._format_artists(["Kapuchon"]) == "Kapuchon"
|
||||||
|
|
||||||
|
def test_two(self):
|
||||||
|
assert traxsource._format_artists(["A", "B"]) == "A & B"
|
||||||
|
|
||||||
|
def test_three(self):
|
||||||
|
assert traxsource._format_artists(["A", "B", "C"]) == "A, B & C"
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
assert traxsource._format_artists([]) == ""
|
||||||
|
|
||||||
|
def test_strips_whitespace(self):
|
||||||
|
assert traxsource._format_artists([" A ", " B "]) == "A & B"
|
||||||
|
|
||||||
|
|
||||||
|
class TestLargeCover:
|
||||||
|
def test_substitutes_52x52_with_500x500(self):
|
||||||
|
url = "https://www.traxsource.com/scripts/image.php/52x52/abc.jpg"
|
||||||
|
assert traxsource._large_cover(url) == "https://www.traxsource.com/scripts/image.php/500x500/abc.jpg"
|
||||||
|
|
||||||
|
def test_no_change_when_no_pattern(self):
|
||||||
|
assert traxsource._large_cover("https://example.com/x.jpg") == "https://example.com/x.jpg"
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
assert traxsource._large_cover("") == ""
|
||||||
|
|
||||||
|
|
||||||
|
class TestExceptions:
|
||||||
|
def test_exceptions_are_subclasses(self):
|
||||||
|
assert issubclass(traxsource.TraxsourceUnreachableError, traxsource.TraxsourceError)
|
||||||
|
assert issubclass(traxsource.TraxsourceParseError, traxsource.TraxsourceError)
|
||||||
Reference in new issue
Block a user