traxsource: GENRES + TraxsourceTrack + list_genres
This commit is contained in:
1 parent
7feda99720
commit
a95912f78c
2 files changed
+108
No files matched your search
@@ -0,0 +1,61 @@
|
||||
"""Fetch Top 100 Traxsource per genere.
|
||||
|
||||
Approccio: session curl_cffi (bypass CF via cookie) + BeautifulSoup HTML scraping.
|
||||
Spec: docs/superpowers/specs/2026-08-01-traxsource-charts-design.md.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
# Generi musicali Traxsource (slug -> (id, display_name)).
|
||||
# Aggiornato via scripts/refresh_traxsource_genres.py. Esclusi "sounds/samples/loops",
|
||||
# "acapella", "beats", "efx-dj-tools", "stems" (non generi ma tipi di prodotto).
|
||||
GENRES: dict = {
|
||||
"afro-house": (27, "Afro House"),
|
||||
"afro-latin-brazilian": (23, "Afro / Latin / Brazilian"),
|
||||
"broken-beat-nu-jazz": (2, "Broken Beat / Nu Jazz"),
|
||||
"classic-house": (12, "Classic House"),
|
||||
"deep-house": (13, "Deep House"),
|
||||
"drum-and-bass": (31, "Drum & Bass"),
|
||||
"electro-house": (11, "Electro House"),
|
||||
"electronica": (5, "Electronica"),
|
||||
"garage": (29, "Garage"),
|
||||
"house": (4, "House"),
|
||||
"jackin-house": (15, "Jackin House"),
|
||||
"leftfield": (14, "Leftfield"),
|
||||
"lounge-chill-out": (1, "Lounge / Chill Out"),
|
||||
"melodic-progressive-house": (19, "Melodic / Progressive House"),
|
||||
"minimal-deep-tech": (16, "Minimal / Deep Tech"),
|
||||
"nu-disco-indie-dance": (17, "Nu Disco / Indie Dance"),
|
||||
"pop-dance": (32, "Pop Dance"),
|
||||
"r-and-b-hip-hop": (6, "R&B / Hip Hop"),
|
||||
"soul-funk-disco": (3, "Soul / Funk / Disco"),
|
||||
"soulful-house": (24, "Soulful House"),
|
||||
"tech-house": (18, "Tech House"),
|
||||
"techno": (20, "Techno"),
|
||||
"world": (30, "World"),
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class TraxsourceTrack:
|
||||
position: int
|
||||
title: str
|
||||
mix: str
|
||||
artists: str
|
||||
label: str
|
||||
traxsource_id: int
|
||||
slug: str
|
||||
image_url: str = ""
|
||||
cover_url_large: str = ""
|
||||
|
||||
|
||||
def list_genres() -> list:
|
||||
result = [
|
||||
{"slug": slug, "id": gid, "name": name}
|
||||
for slug, (gid, name) in GENRES.items()
|
||||
]
|
||||
result.sort(key=lambda g: g["name"].casefold())
|
||||
return result
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Test per core.traxsource."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from core import traxsource
|
||||
|
||||
|
||||
class TestListGenres:
|
||||
def test_returns_list_of_dicts(self):
|
||||
result = traxsource.list_genres()
|
||||
assert isinstance(result, list)
|
||||
assert len(result) >= 10
|
||||
for g in result:
|
||||
assert set(g.keys()) == {"slug", "id", "name"}
|
||||
assert isinstance(g["slug"], str) and g["slug"]
|
||||
assert isinstance(g["id"], int) and g["id"] > 0
|
||||
assert isinstance(g["name"], str) and g["name"]
|
||||
|
||||
def test_sorted_alphabetically_by_name(self):
|
||||
result = traxsource.list_genres()
|
||||
names = [g["name"] for g in result]
|
||||
assert names == sorted(names, key=str.casefold)
|
||||
|
||||
def test_tech_house_present(self):
|
||||
result = traxsource.list_genres()
|
||||
slugs = [g["slug"] for g in result]
|
||||
assert "tech-house" in slugs
|
||||
|
||||
|
||||
class TestTraxsourceTrack:
|
||||
def test_frozen(self):
|
||||
t = traxsource.TraxsourceTrack(
|
||||
position=1, title="X", mix="Y", artists="A", label="L",
|
||||
traxsource_id=1, slug="x",
|
||||
)
|
||||
with pytest.raises(Exception):
|
||||
t.title = "Z"
|
||||
|
||||
def test_defaults(self):
|
||||
t = traxsource.TraxsourceTrack(
|
||||
position=1, title="X", mix="", artists="A", label="",
|
||||
traxsource_id=1, slug="x",
|
||||
)
|
||||
assert t.image_url == ""
|
||||
assert t.cover_url_large == ""
|
||||
Reference in new issue
Block a user