Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,12 +1,11 @@
|
|
1 |
import base64
|
2 |
import logging
|
3 |
-
from typing import Optional
|
4 |
-
from functools import lru_cache
|
5 |
from fastapi import FastAPI, HTTPException, Request
|
6 |
import requests
|
7 |
from bs4 import BeautifulSoup
|
8 |
import os
|
9 |
-
from datetime import datetime
|
10 |
import time
|
11 |
|
12 |
# Configure logging
|
@@ -27,16 +26,44 @@ app = FastAPI(title="Spotify Track API",
|
|
27 |
SPOTIFY_API_URL = "https://api.spotify.com/v1"
|
28 |
SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
29 |
SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
|
30 |
-
TOKEN_EXPIRY =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
# Custom exception for Spotify API errors
|
33 |
class SpotifyAPIError(Exception):
|
34 |
pass
|
35 |
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
39 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
logger.info("Requesting new Spotify access token")
|
41 |
start_time = time.time()
|
42 |
|
@@ -56,9 +83,11 @@ def get_spotify_token():
|
|
56 |
if auth_response.status_code != 200:
|
57 |
raise SpotifyAPIError(f"Failed to get token: {auth_response.text}")
|
58 |
|
59 |
-
|
60 |
-
|
61 |
-
|
|
|
|
|
62 |
|
63 |
except requests.exceptions.RequestException as e:
|
64 |
logger.error(f"Network error during token request: {str(e)}")
|
@@ -85,7 +114,7 @@ async def get_track_download_url(track_id: str) -> str:
|
|
85 |
for api_url in apis:
|
86 |
try:
|
87 |
logger.info(f"Attempting to get download URL from: {api_url}")
|
88 |
-
response = requests.get(api_url, timeout=
|
89 |
if response.status_code == 200:
|
90 |
download_url = response.json().get("url")
|
91 |
if download_url:
|
@@ -133,6 +162,18 @@ async def get_track(
|
|
133 |
timeout=10
|
134 |
)
|
135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
if response.status_code != 200:
|
137 |
logger.error(f"Request {request_id} - Spotify API error: {response.text}")
|
138 |
raise HTTPException(
|
@@ -171,7 +212,11 @@ async def health_check():
|
|
171 |
try:
|
172 |
# Test Spotify API token generation
|
173 |
token = get_spotify_token()
|
174 |
-
return {
|
|
|
|
|
|
|
|
|
175 |
except Exception as e:
|
176 |
logger.error(f"Health check failed: {str(e)}")
|
177 |
return {"status": "unhealthy", "error": str(e)}
|
|
|
1 |
import base64
|
2 |
import logging
|
3 |
+
from typing import Optional, Dict
|
|
|
4 |
from fastapi import FastAPI, HTTPException, Request
|
5 |
import requests
|
6 |
from bs4 import BeautifulSoup
|
7 |
import os
|
8 |
+
from datetime import datetime, timedelta
|
9 |
import time
|
10 |
|
11 |
# Configure logging
|
|
|
26 |
SPOTIFY_API_URL = "https://api.spotify.com/v1"
|
27 |
SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
|
28 |
SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
|
29 |
+
TOKEN_EXPIRY = 3500 # Slightly less than 1 hour to ensure token refresh before expiration
|
30 |
+
|
31 |
+
# Token cache
|
32 |
+
class TokenCache:
|
33 |
+
def __init__(self):
|
34 |
+
self.token: Optional[str] = None
|
35 |
+
self.expiry_time: Optional[datetime] = None
|
36 |
+
|
37 |
+
def set_token(self, token: str):
|
38 |
+
self.token = token
|
39 |
+
self.expiry_time = datetime.now() + timedelta(seconds=TOKEN_EXPIRY)
|
40 |
+
|
41 |
+
def get_token(self) -> Optional[str]:
|
42 |
+
if not self.token or not self.expiry_time or datetime.now() >= self.expiry_time:
|
43 |
+
return None
|
44 |
+
return self.token
|
45 |
+
|
46 |
+
def is_expired(self) -> bool:
|
47 |
+
return not self.token or not self.expiry_time or datetime.now() >= self.expiry_time
|
48 |
+
|
49 |
+
token_cache = TokenCache()
|
50 |
|
51 |
# Custom exception for Spotify API errors
|
52 |
class SpotifyAPIError(Exception):
|
53 |
pass
|
54 |
|
55 |
+
def get_spotify_token() -> str:
|
56 |
+
"""
|
57 |
+
Get Spotify access token with expiration handling.
|
58 |
+
Returns a valid token, either from cache or by requesting a new one.
|
59 |
+
"""
|
60 |
try:
|
61 |
+
# Check if we have a valid cached token
|
62 |
+
cached_token = token_cache.get_token()
|
63 |
+
if cached_token:
|
64 |
+
logger.info("Using cached Spotify token")
|
65 |
+
return cached_token
|
66 |
+
|
67 |
logger.info("Requesting new Spotify access token")
|
68 |
start_time = time.time()
|
69 |
|
|
|
83 |
if auth_response.status_code != 200:
|
84 |
raise SpotifyAPIError(f"Failed to get token: {auth_response.text}")
|
85 |
|
86 |
+
new_token = auth_response.json()['access_token']
|
87 |
+
token_cache.set_token(new_token)
|
88 |
+
|
89 |
+
logger.info(f"New token obtained successfully in {time.time() - start_time:.2f}s")
|
90 |
+
return new_token
|
91 |
|
92 |
except requests.exceptions.RequestException as e:
|
93 |
logger.error(f"Network error during token request: {str(e)}")
|
|
|
114 |
for api_url in apis:
|
115 |
try:
|
116 |
logger.info(f"Attempting to get download URL from: {api_url}")
|
117 |
+
response = requests.get(api_url, timeout=10)
|
118 |
if response.status_code == 200:
|
119 |
download_url = response.json().get("url")
|
120 |
if download_url:
|
|
|
162 |
timeout=10
|
163 |
)
|
164 |
|
165 |
+
# Handle token expiration
|
166 |
+
if response.status_code == 401:
|
167 |
+
logger.info("Token expired, requesting new token")
|
168 |
+
token_cache.token = None # Clear expired token
|
169 |
+
access_token = get_spotify_token()
|
170 |
+
# Retry the request with new token
|
171 |
+
response = requests.get(
|
172 |
+
f"{SPOTIFY_API_URL}/tracks/{track_id}",
|
173 |
+
headers={"Authorization": f"Bearer {access_token}"},
|
174 |
+
timeout=10
|
175 |
+
)
|
176 |
+
|
177 |
if response.status_code != 200:
|
178 |
logger.error(f"Request {request_id} - Spotify API error: {response.text}")
|
179 |
raise HTTPException(
|
|
|
212 |
try:
|
213 |
# Test Spotify API token generation
|
214 |
token = get_spotify_token()
|
215 |
+
return {
|
216 |
+
"status": "healthy",
|
217 |
+
"spotify_auth": "ok",
|
218 |
+
"token_expires_in": token_cache.expiry_time.timestamp() - datetime.now().timestamp() if token_cache.expiry_time else None
|
219 |
+
}
|
220 |
except Exception as e:
|
221 |
logger.error(f"Health check failed: {str(e)}")
|
222 |
return {"status": "unhealthy", "error": str(e)}
|