From f1e57905c1a0b1cee4ece8c0cc1caf5106c15126 Mon Sep 17 00:00:00 2001 From: luciano Date: Tue, 14 Jul 2026 16:20:21 +0200 Subject: [PATCH] beatport: estrazione __NEXT_DATA__ + eccezioni tipizzate --- core/beatport.py | 31 +++++++++++++++++++++++++++++++ tests/test_beatport.py | 17 +++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/core/beatport.py b/core/beatport.py index 890c6e1..8910548 100644 --- a/core/beatport.py +++ b/core/beatport.py @@ -7,6 +7,8 @@ Vedi docs/superpowers/specs/2026-07-14-beatport-charts-design.md. from __future__ import annotations +import json +import re from dataclasses import dataclass @@ -33,6 +35,35 @@ class BeatportTrack: return f"{self.artists} {self.title}" +class BeatportError(Exception): + """Base per errori Beatport.""" + + +class BeatportUnreachableError(BeatportError): + """Rete o server Beatport non raggiungibile / 5xx.""" + + +class BeatportParseError(BeatportError): + """HTML/JSON ricevuto ma non conforme allo schema atteso.""" + + +_NEXT_DATA_RE = re.compile( + r'', + re.DOTALL, +) + + +def _extract_next_data(html: str) -> dict: + """Estrae il payload JSON dallo script <__NEXT_DATA__> di Next.js.""" + m = _NEXT_DATA_RE.search(html) + if not m: + raise BeatportParseError("__NEXT_DATA__ non trovato nella pagina Beatport") + try: + return json.loads(m.group(1)) + except json.JSONDecodeError as e: + raise BeatportParseError(f"__NEXT_DATA__ JSON malformato: {e}") from e + + # Mappa slug URL Beatport -> (numeric_id, display_name) # Enumerata via scripts/refresh_beatport_genres.py (Task 1). # Copia dell'output di /tmp/genres_output.txt (31 generi). diff --git a/tests/test_beatport.py b/tests/test_beatport.py index fb4ddd5..1e533dd 100644 --- a/tests/test_beatport.py +++ b/tests/test_beatport.py @@ -66,3 +66,20 @@ class TestBeatportTrack: ) with pytest.raises(Exception): t.title = "Z" # frozen=True impedisce mutazione + + +class TestExtractNextData: + def test_extracts_json_from_valid_html(self, fixtures_dir): + html = (fixtures_dir / "beatport_melodic_top100.html").read_text() + data = beatport._extract_next_data(html) + assert isinstance(data, dict) + assert "props" in data + + def test_missing_script_raises(self): + with pytest.raises(beatport.BeatportParseError, match="__NEXT_DATA__ non trovato"): + beatport._extract_next_data("nulla") + + def test_malformed_json_raises(self): + broken = '' + with pytest.raises(beatport.BeatportParseError, match="JSON malformato"): + beatport._extract_next_data(broken)