From 66647a93e1e86af20656648cfda91e760b6db060 Mon Sep 17 00:00:00 2001 From: MJ Rossetti Date: Sun, 25 Aug 2024 22:22:46 -0400 Subject: [PATCH 1/2] Upload file to drive --- app/drive_service.py | 59 + app/google_apis.py | 10 + requirements.txt | 3 + .../Spotify_API_Demo_(Summer_2024).ipynb | 2736 +++++++++++++++++ 4 files changed, 2808 insertions(+) create mode 100644 app/drive_service.py create mode 100644 app/google_apis.py create mode 100644 test/notebooks/Spotify_API_Demo_(Summer_2024).ipynb diff --git a/app/drive_service.py b/app/drive_service.py new file mode 100644 index 0000000..9c43b30 --- /dev/null +++ b/app/drive_service.py @@ -0,0 +1,59 @@ +from google.oauth2 import service_account +from googleapiclient.discovery import build +from googleapiclient.http import MediaFileUpload + +from app.google_apis import GOOGLE_CREDENTIALS_FILEPATH + + + +def upload_file_to_drive(file_path, folder_id, credentials_json=GOOGLE_CREDENTIALS_FILEPATH): + """ + Upload a file to Google Drive. + + :param credentials_json: Path to the credentials JSON file. + :param file_path: Path to the local file to be uploaded. + :param folder_id: (Optional) Google Drive folder ID where the file will be uploaded. + If not provided, the file will be uploaded to the root directory. + :return: The file ID of the uploaded file. + """ + + # need to enable the google drive api from the google cloud console first + credentials = service_account.Credentials.from_service_account_file( + credentials_json, scopes=['https://www.googleapis.com/auth/drive.file'] + ) + # Build the Drive API service + drive_service = build('drive', 'v3', credentials=credentials) + + # Define the file metadata + file_metadata = {'name': file_path.split('/')[-1]} # # Extracts the file name from the path + + if folder_id: + file_metadata['parents'] = [folder_id] + + breakpoint() + + media = MediaFileUpload(file_path, resumable=True) + + #uploaded_file = drive_service.files().create(body=file_metadata, media_body=media, fields='id').execute() + # get all fields: + #uploaded_file = drive_service.files().create(body=file_metadata, media_body=media, fields='*').execute() + fields = ", ".join(["id", "name", "originalFilename", "parents", "quotaBytesUsed", "size", "webContentLink", "webViewLink"]) + uploaded_file = drive_service.files().create(body=file_metadata, media_body=media, fields=fields).execute() + + # Return the file ID + return uploaded_file.get('id') + +# Example usage +if __name__ == '__main__': + + + import os + + filepath = os.path.join(os.path.dirname(__file__), "..", "test", "notebooks", "Spotify_API_Demo_(Summer_2024).ipynb") + assert os.path.isfile(filepath) + + # need to share folder > editor access with service account email address + folder_id = '1OIPp3BkjUwMZSpKMn_PW9RozdpmRqBno' + + file_id = upload_file_to_drive(filepath=filepath, folder_id=folder_id) + print(f"File uploaded successfully with ID: {file_id}") diff --git a/app/google_apis.py b/app/google_apis.py new file mode 100644 index 0000000..030a7c8 --- /dev/null +++ b/app/google_apis.py @@ -0,0 +1,10 @@ + +import os + +from dotenv import load_dotenv + +load_dotenv() + +# google credentials: +DEFAULT_FILEPATH = os.path.join(os.path.dirname(__file__), "..", "google-credentials.json") +GOOGLE_CREDENTIALS_FILEPATH = os.getenv("GOOGLE_CREDENTIALS_FILEPATH", default=DEFAULT_FILEPATH) diff --git a/requirements.txt b/requirements.txt index 8fb6aa2..af6d041 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,9 @@ Authlib==1.3.0 # ==1.0.0 gspread==6.0.2 gspread_models==1.0.2 +google-api-python-client # ==2.142.0 + + # production web server: gunicorn diff --git a/test/notebooks/Spotify_API_Demo_(Summer_2024).ipynb b/test/notebooks/Spotify_API_Demo_(Summer_2024).ipynb new file mode 100644 index 0000000..65b8350 --- /dev/null +++ b/test/notebooks/Spotify_API_Demo_(Summer_2024).ipynb @@ -0,0 +1,2736 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "collapsed_sections": [ + "TeoUz227W0r5" + ], + "toc_visible": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "Mkx6kqODzk5r" + }, + "source": [ + "## References\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mp6ti6rBE5sx" + }, + "source": [ + "\n", + "### The Spotify API\n", + "\n", + " + https://developer.spotify.com/documentation/web-api/\n", + " + https://developer.spotify.com/documentation/general/guides/authorization-guide\n", + " + https://developer.spotify.com/documentation/general/guides/scopes/\n", + "\n", + "### The `spotipy` Package\n", + "\n", + "The `spotipy` package provides an interface into the Spotify API.\n", + "\n", + " + https://github.com/plamere/spotipy\n", + " + https://github.com/plamere/spotipy#quick-start\n", + " + https://spotipy.readthedocs.io/en/latest/\n", + " + https://spotipy.readthedocs.io/en/latest/#client-credentials-flow\n", + " + https://spotipy.readthedocs.io/en/latest/#authorization-code-flow\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cChEVsHSzqsU" + }, + "source": [ + "\n", + "## Setup\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Waq5k4czWmCS" + }, + "source": [ + "Installing the `spotipy` package into the notebook environment:" + ] + }, + { + "cell_type": "code", + "source": [ + "%%capture\n", + "!pip install spotipy" + ], + "metadata": { + "id": "yEWKc8CYXac8" + }, + "execution_count": 9, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "!pip list | grep spotipy" + ], + "metadata": { + "id": "F8l2YiorWzq3", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "ed20fd1c-ded8-4b69-9c99-0231308b9366" + }, + "execution_count": 10, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "spotipy 2.24.0\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "h81rvMYAWkoM" + }, + "source": [ + "Create a [Spotify API Client application](https://developer.spotify.com/dashboard/applications/), note its credentials, then set them as notebook secrets called `SPOTIPY_CLIENT_ID` and `SPOTIPY_CLIENT_SECRET` respectively." + ] + }, + { + "cell_type": "markdown", + "source": [ + "Accessing the notebook secrets:" + ], + "metadata": { + "id": "6OSd4TMxF9Ft" + } + }, + { + "cell_type": "code", + "source": [ + "from google.colab import userdata\n", + "\n", + "SPOTIPY_CLIENT_ID = userdata.get(\"SPOTIPY_CLIENT_ID\")\n", + "SPOTIPY_CLIENT_SECRET = userdata.get(\"SPOTIPY_CLIENT_SECRET\")" + ], + "metadata": { + "id": "l1QEGRWxCaN8" + }, + "execution_count": 11, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NnlooeDez9oC" + }, + "source": [ + "## Investigation" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "BBUDnbNrWw7A" + }, + "source": [ + "### Info Inputs" + ] + }, + { + "cell_type": "markdown", + "source": [ + "Enter your search term:" + ], + "metadata": { + "id": "REU8CG917Ru1" + } + }, + { + "cell_type": "code", + "metadata": { + "id": "WaEb9THPWvWJ" + }, + "source": [ + "search_term = \"Dua Lipa\"" + ], + "execution_count": 12, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TeoUz227W0r5" + }, + "source": [ + "### Info Processing" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "OQ3QJr6GE3qy", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "c9bc60c4-4222-4830-cc5c-c458920a1cb1" + }, + "source": [ + "\n", + "from spotipy import Spotify\n", + "from spotipy.oauth2 import SpotifyClientCredentials\n", + "\n", + "creds = SpotifyClientCredentials(client_id=SPOTIPY_CLIENT_ID, client_secret=SPOTIPY_CLIENT_SECRET)\n", + "client = Spotify(client_credentials_manager=creds)\n", + "print(\"CLIENT:\", type(client))" + ], + "execution_count": 30, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "CLIENT: \n" + ] + } + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "MlC-4x3OW_FP", + "outputId": "578cd10c-cdff-4370-8beb-f15734bbae33" + }, + "source": [ + "# api request\n", + "results = client.search(q=search_term, limit=20)\n", + "print(results.keys())" + ], + "execution_count": 31, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "dict_keys(['tracks'])\n" + ] + } + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "CL1KFdaGXK3j", + "outputId": "142458fb-68f3-4d60-ca3e-391daa559126" + }, + "source": [ + "print(results[\"tracks\"].keys())" + ], + "execution_count": 32, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "dict_keys(['href', 'items', 'limit', 'next', 'offset', 'previous', 'total'])\n" + ] + } + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "2_9i4QcSXTG4", + "outputId": "e9b9027d-d32e-4569-e4df-1d35103b0670" + }, + "source": [ + "tracks = results[\"tracks\"][\"items\"]\n", + "print(tracks[0].keys())" + ], + "execution_count": 33, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "dict_keys(['album', 'artists', 'available_markets', 'disc_number', 'duration_ms', 'explicit', 'external_ids', 'external_urls', 'href', 'id', 'is_local', 'name', 'popularity', 'preview_url', 'track_number', 'type', 'uri'])\n" + ] + } + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Rm198tATX3Dy", + "outputId": "329616fe-3bc8-4547-b7a6-e5397d56095a" + }, + "source": [ + "print(tracks[0].keys())" + ], + "execution_count": 34, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "dict_keys(['album', 'artists', 'available_markets', 'disc_number', 'duration_ms', 'explicit', 'external_ids', 'external_urls', 'href', 'id', 'is_local', 'name', 'popularity', 'preview_url', 'track_number', 'type', 'uri'])\n" + ] + } + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "MCqpPBgGYspH", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "766e4a7b-3ff2-4c3c-dae7-a4d67cb2c0c4" + }, + "source": [ + "try:\n", + " del tracks[0][\"album\"][\"available_markets\"]\n", + "except:\n", + " pass\n", + "\n", + "try:\n", + " del tracks[0][\"available_markets\"]\n", + "except:\n", + " pass\n", + "\n", + "tracks[0]" + ], + "execution_count": 36, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "{'album': {'album_type': 'album',\n", + " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/6M2wZ9GZgrQXHCFfjv46we'},\n", + " 'href': 'https://api.spotify.com/v1/artists/6M2wZ9GZgrQXHCFfjv46we',\n", + " 'id': '6M2wZ9GZgrQXHCFfjv46we',\n", + " 'name': 'Dua Lipa',\n", + " 'type': 'artist',\n", + " 'uri': 'spotify:artist:6M2wZ9GZgrQXHCFfjv46we'}],\n", + " 'external_urls': {'spotify': 'https://open.spotify.com/album/7fJJK56U9fHixgO0HQkhtI'},\n", + " 'href': 'https://api.spotify.com/v1/albums/7fJJK56U9fHixgO0HQkhtI',\n", + " 'id': '7fJJK56U9fHixgO0HQkhtI',\n", + " 'images': [{'height': 640,\n", + " 'url': 'https://i.scdn.co/image/ab67616d0000b2734bc66095f8a70bc4e6593f4f',\n", + " 'width': 640},\n", + " {'height': 300,\n", + " 'url': 'https://i.scdn.co/image/ab67616d00001e024bc66095f8a70bc4e6593f4f',\n", + " 'width': 300},\n", + " {'height': 64,\n", + " 'url': 'https://i.scdn.co/image/ab67616d000048514bc66095f8a70bc4e6593f4f',\n", + " 'width': 64}],\n", + " 'name': 'Future Nostalgia',\n", + " 'release_date': '2020-03-27',\n", + " 'release_date_precision': 'day',\n", + " 'total_tracks': 11,\n", + " 'type': 'album',\n", + " 'uri': 'spotify:album:7fJJK56U9fHixgO0HQkhtI'},\n", + " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/6M2wZ9GZgrQXHCFfjv46we'},\n", + " 'href': 'https://api.spotify.com/v1/artists/6M2wZ9GZgrQXHCFfjv46we',\n", + " 'id': '6M2wZ9GZgrQXHCFfjv46we',\n", + " 'name': 'Dua Lipa',\n", + " 'type': 'artist',\n", + " 'uri': 'spotify:artist:6M2wZ9GZgrQXHCFfjv46we'}],\n", + " 'disc_number': 1,\n", + " 'duration_ms': 203807,\n", + " 'explicit': False,\n", + " 'external_ids': {'isrc': 'GBAHT1901299'},\n", + " 'external_urls': {'spotify': 'https://open.spotify.com/track/39LLxExYz6ewLAcYrzQQyP'},\n", + " 'href': 'https://api.spotify.com/v1/tracks/39LLxExYz6ewLAcYrzQQyP',\n", + " 'id': '39LLxExYz6ewLAcYrzQQyP',\n", + " 'is_local': False,\n", + " 'name': 'Levitating',\n", + " 'popularity': 78,\n", + " 'preview_url': 'https://p.scdn.co/mp3-preview/ac28d1b0be285ed3bfd8e9fa5fad133776d7cf36?cid=d7df2abc82674544a78cb3f39fd7d585',\n", + " 'track_number': 5,\n", + " 'type': 'track',\n", + " 'uri': 'spotify:track:39LLxExYz6ewLAcYrzQQyP'}" + ] + }, + "metadata": {}, + "execution_count": 36 + } + ] + }, + { + "cell_type": "code", + "source": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 141 + }, + "id": "e7bonjdRIJBY", + "outputId": "07045c41-06f4-4c4b-90ee-1431e608df26" + }, + "execution_count": 29, + "outputs": [ + { + "output_type": "error", + "ename": "TypeError", + "evalue": "'SpotifyTracks' object is not subscriptable", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mtracks\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m: 'SpotifyTracks' object is not subscriptable" + ] + } + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "ZxrDNyFJaICs" + }, + "source": [ + "# convert image URL to html\n", + "# credit to: https://towardsdatascience.com/rendering-images-inside-a-pandas-dataframe-3631a4883f60\n", + "\n", + "def img_html(url):\n", + " return ''\n", + "\n", + "def preview_html(url):\n", + " if url:\n", + " return 'Listen on Spotify'\n", + " else:\n", + " return None" + ], + "execution_count": 19, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "vp2YsXYoXhqS" + }, + "source": [ + "records = []\n", + "# parsing the response\n", + "for index, track in enumerate(tracks):\n", + " #print(' ', index, \"|\", track['name'], \"|\", track[\"artists\"][0][\"name\"])\n", + " record = {\n", + " \"index\": index,\n", + " \"name\": track['name'],\n", + " \"artist\": track[\"artists\"][0][\"name\"],\n", + " #\"duration_ms\": track['duration_ms'],\n", + " #\"explicit\": track['explicit'],\n", + " \"popularity\": track[\"popularity\"],\n", + " \"preview_url\": preview_html(track[\"preview_url\"]),\n", + " \"album_art\": img_html(track[\"album\"][\"images\"][0][\"url\"])\n", + " }\n", + " records.append(record)" + ], + "execution_count": 20, + "outputs": [] + }, + { + "cell_type": "code", + "metadata": { + "id": "rxnK4mryXmIj", + "colab": { + "base_uri": "https://localhost:8080/", + "height": 292 + }, + "outputId": "89405499-91a7-44b0-9729-15892c64a502" + }, + "source": [ + "from pandas import DataFrame\n", + "\n", + "tracks_df = DataFrame(records)\n", + "tracks_df.head()" + ], + "execution_count": 21, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " index name artist popularity \\\n", + "0 0 Levitating Dua Lipa 78 \n", + "1 1 New Rules Dua Lipa 81 \n", + "2 2 Dance The Night - From Barbie The Album Dua Lipa 84 \n", + "3 3 Dance The Night Dua Lipa 72 \n", + "4 4 Dua Lipa Jack Harlow 62 \n", + "\n", + " preview_url \\\n", + "0 \n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
indexnameartistpopularitypreview_urlalbum_art
00LevitatingDua Lipa78<a href=\"https://p.scdn.co/mp3-preview/ac28d1b...<img src=\"https://i.scdn.co/image/ab67616d0000...
11New RulesDua Lipa81<a href=\"https://p.scdn.co/mp3-preview/e4f2ca2...<img src=\"https://i.scdn.co/image/ab67616d0000...
22Dance The Night - From Barbie The AlbumDua Lipa84<a href=\"https://p.scdn.co/mp3-preview/acaea04...<img src=\"https://i.scdn.co/image/ab67616d0000...
33Dance The NightDua Lipa72<a href=\"https://p.scdn.co/mp3-preview/acaea04...<img src=\"https://i.scdn.co/image/ab67616d0000...
44Dua LipaJack Harlow62<a href=\"https://p.scdn.co/mp3-preview/a67e101...<img src=\"https://i.scdn.co/image/ab67616d0000...
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "variable_name": "tracks_df", + "summary": "{\n \"name\": \"tracks_df\",\n \"rows\": 20,\n \"fields\": [\n {\n \"column\": \"index\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 5,\n \"min\": 0,\n \"max\": 19,\n \"num_unique_values\": 20,\n \"samples\": [\n 0,\n 17,\n 15\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 17,\n \"samples\": [\n \"Levitating\",\n \"New Rules\",\n \"Don't Start Now\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"artist\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 3,\n \"samples\": [\n \"Dua Lipa\",\n \"Jack Harlow\",\n \"Calvin Harris\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"popularity\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 7,\n \"min\": 58,\n \"max\": 85,\n \"num_unique_values\": 13,\n \"samples\": [\n 79,\n 83,\n 78\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"preview_url\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 16,\n \"samples\": [\n \"
Listen on Spotify\",\n \"Listen on Spotify\",\n \"Listen on Spotify\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"album_art\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 12,\n \"samples\": [\n \"\",\n \"\",\n \"\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}" + } + }, + "metadata": {}, + "execution_count": 21 + } + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "wca59E8Y0FRM" + }, + "source": [ + "from IPython.core.display import HTML\n", + "\n", + "# displaying the dataframe as HTML, with HTML links and images:\n", + "tracks_table = HTML(tracks_df.to_html(escape=False, index=False, formatters=dict(Icon=img_html)))" + ], + "execution_count": 22, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "N5tRefYiW3qW" + }, + "source": [ + "### Info Outputs" + ] + }, + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "OHuLZ5RkaWis", + "outputId": "b5d7c697-c14d-4dd7-a934-9cf2410726d7" + }, + "source": [ + "display(tracks_table)" + ], + "execution_count": 23, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
indexnameartistpopularitypreview_urlalbum_art
0LevitatingDua Lipa78Listen on Spotify
1New RulesDua Lipa81Listen on Spotify
2Dance The Night - From Barbie The AlbumDua Lipa84Listen on Spotify
3Dance The NightDua Lipa72Listen on Spotify
4Dua LipaJack Harlow62Listen on Spotify
5Don't Start NowDua Lipa84Listen on Spotify
6One Kiss (with Dua Lipa)Calvin Harris85Listen on Spotify
7HoudiniDua Lipa80Listen on Spotify
8IDGAFDua Lipa77Listen on Spotify
9Levitating (feat. DaBaby)Dua Lipa82Listen on Spotify
10PhysicalDua Lipa77Listen on Spotify
11Break My HeartDua Lipa77Listen on Spotify
12Training SeasonDua Lipa83Listen on Spotify
13Love AgainDua Lipa72Listen on Spotify
14HoudiniDua Lipa82Listen on Spotify
15We're GoodDua Lipa69Listen on Spotify
16IllusionDua Lipa79Listen on Spotify
17Training SeasonDua Lipa81Listen on Spotify
18IllusionDua Lipa80Listen on Spotify
19CoolDua Lipa58Listen on Spotify
" + ] + }, + "metadata": {} + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Refactored (Functional)" + ], + "metadata": { + "id": "EiUnozoy7hcC" + } + }, + { + "cell_type": "code", + "source": [ + "\n", + "from spotipy import Spotify\n", + "from spotipy.oauth2 import SpotifyClientCredentials\n", + "\n", + "def get_tracks(search_term):\n", + " creds = SpotifyClientCredentials(client_id=SPOTIPY_CLIENT_ID, client_secret=SPOTIPY_CLIENT_SECRET)\n", + " client = Spotify(client_credentials_manager=creds)\n", + "\n", + " results = client.search(q=search_term, limit=20) # # api request\n", + " tracks = results[\"tracks\"][\"items\"]\n", + "\n", + " records = []\n", + " for track in tracks:\n", + " record = {\n", + " \"name\": track['name'],\n", + " \"artist\": track[\"artists\"][0][\"name\"],\n", + " \"duration_ms\": track['duration_ms'],\n", + " \"explicit\": track['explicit'],\n", + " \"popularity\": track[\"popularity\"],\n", + " \"album_img_url\": track[\"album\"][\"images\"][0][\"url\"],\n", + " \"preview_url\": track[\"preview_url\"],\n", + " # separation of responsibilities. move display logic out:\n", + " #\"preview_html\": preview_html(track[\"preview_url\"]),\n", + " #\"album_art\": img_html(track[\"album\"][\"images\"][0][\"url\"]),\n", + " }\n", + " records.append(record)\n", + " return records\n" + ], + "metadata": { + "id": "gqJjzXqH7lyU" + }, + "execution_count": 24, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "query = \"Dua Lipa\"\n", + "print(query)\n", + "\n", + "tracks = get_tracks(query)\n", + "print(len(tracks))\n", + "\n", + "tracks[0]" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Bevw6hMX8meS", + "outputId": "faff992b-dd09-425e-f9e8-cccf44d452cc" + }, + "execution_count": 25, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Dua Lipa\n", + "20\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "{'name': 'Levitating',\n", + " 'artist': 'Dua Lipa',\n", + " 'duration_ms': 203807,\n", + " 'explicit': False,\n", + " 'popularity': 78,\n", + " 'album_img_url': 'https://i.scdn.co/image/ab67616d0000b2734bc66095f8a70bc4e6593f4f',\n", + " 'preview_url': 'https://p.scdn.co/mp3-preview/ac28d1b0be285ed3bfd8e9fa5fad133776d7cf36?cid=d7df2abc82674544a78cb3f39fd7d585'}" + ] + }, + "metadata": {}, + "execution_count": 25 + } + ] + }, + { + "cell_type": "code", + "source": [ + "from pandas import DataFrame\n", + "from IPython.core.display import HTML\n", + "\n", + "# all the html-specific display logic\n", + "\n", + "def img_html(url):\n", + " return ''\n", + "\n", + "def preview_html(url):\n", + " if url:\n", + " return 'Listen on Spotify'\n", + " else:\n", + " return None\n", + "\n", + "# displaying the dataframe as HTML, with HTML links and images:\n", + "\n", + "tracks_df = DataFrame(tracks)\n", + "tracks_df[\"preview_html\"] = tracks_df[\"preview_url\"].apply(preview_html)\n", + "tracks_df[\"album_art\"] = tracks_df[\"album_img_url\"].apply(img_html)\n", + "tracks_df.drop(columns=[\"album_img_url\", \"preview_url\"], inplace=True)\n", + "tracks_table = HTML(tracks_df.to_html(escape=False, index=False, formatters=dict(Icon=img_html)))\n", + "display(tracks_table)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "ZQpfBQJd7lur", + "outputId": "c4538842-8905-4ca6-aa24-f6bbbf136a91" + }, + "execution_count": 26, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nameartistduration_msexplicitpopularitypreview_htmlalbum_art
LevitatingDua Lipa203807False78Listen on Spotify
New RulesDua Lipa209320False81Listen on Spotify
Dance The Night - From Barbie The AlbumDua Lipa176579False84Listen on Spotify
Dance The NightDua Lipa176579False72Listen on Spotify
Dua LipaJack Harlow135053True62Listen on Spotify
Don't Start NowDua Lipa183290False84Listen on Spotify
One Kiss (with Dua Lipa)Calvin Harris214846False85Listen on Spotify
HoudiniDua Lipa185917False80Listen on Spotify
IDGAFDua Lipa217946True77Listen on Spotify
Levitating (feat. DaBaby)Dua Lipa203064False82Listen on Spotify
PhysicalDua Lipa193829False77Listen on Spotify
Break My HeartDua Lipa221820False77Listen on Spotify
Training SeasonDua Lipa209487False83Listen on Spotify
Love AgainDua Lipa258004False72Listen on Spotify
HoudiniDua Lipa185917False82Listen on Spotify
We're GoodDua Lipa165506False69Listen on Spotify
IllusionDua Lipa188143False79Listen on Spotify
Training SeasonDua Lipa209487False81Listen on Spotify
IllusionDua Lipa188143False80Listen on Spotify
CoolDua Lipa209583False58Listen on Spotify
" + ] + }, + "metadata": {} + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "## Refactoring (Object Oriented)" + ], + "metadata": { + "id": "ZMFf0HosAhUr" + } + }, + { + "cell_type": "markdown", + "source": [ + "If we need to extend the capabilities to make different requests using the client, that motivates us to share the client across all the different methods:" + ], + "metadata": { + "id": "UjzBbGD1AqHw" + } + }, + { + "cell_type": "code", + "source": [ + "from spotipy import Spotify\n", + "from spotipy.oauth2 import SpotifyClientCredentials\n", + "\n", + "# all the data fetching logic\n", + "\n", + "class SpotifyService:\n", + " def __init__(self, client_id=SPOTIPY_CLIENT_ID, client_secret=SPOTIPY_CLIENT_SECRET):\n", + " self.creds = SpotifyClientCredentials(client_id=client_id, client_secret=client_secret)\n", + " self.client = Spotify(client_credentials_manager=self.creds)\n", + "\n", + " def get_tracks(self, query):\n", + " results = self.client.search(q=query, limit=20) # # api request\n", + " return results[\"tracks\"][\"items\"]\n", + "\n", + " def get_markets(self):\n", + " result = self.client.available_markets()\n", + " return result[\"markets\"]\n", + "\n", + " def get_audio_features(self, track_ids):\n", + " return self.client.audio_features(track_ids)\n", + "\n", + "\n", + "service = SpotifyService()\n", + "tracks = service.get_tracks(\"Dua Lipa\")\n", + "print(len(tracks))\n", + "\n", + "del tracks[0][\"album\"][\"available_markets\"]\n", + "del tracks[0][\"available_markets\"]\n", + "tracks[0]" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "0vL6r-iJAi_8", + "outputId": "14216f34-4d65-408f-b734-84a916299693" + }, + "execution_count": 45, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "20\n" + ] + }, + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "{'album': {'album_type': 'album',\n", + " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/6M2wZ9GZgrQXHCFfjv46we'},\n", + " 'href': 'https://api.spotify.com/v1/artists/6M2wZ9GZgrQXHCFfjv46we',\n", + " 'id': '6M2wZ9GZgrQXHCFfjv46we',\n", + " 'name': 'Dua Lipa',\n", + " 'type': 'artist',\n", + " 'uri': 'spotify:artist:6M2wZ9GZgrQXHCFfjv46we'}],\n", + " 'external_urls': {'spotify': 'https://open.spotify.com/album/7fJJK56U9fHixgO0HQkhtI'},\n", + " 'href': 'https://api.spotify.com/v1/albums/7fJJK56U9fHixgO0HQkhtI',\n", + " 'id': '7fJJK56U9fHixgO0HQkhtI',\n", + " 'images': [{'height': 640,\n", + " 'url': 'https://i.scdn.co/image/ab67616d0000b2734bc66095f8a70bc4e6593f4f',\n", + " 'width': 640},\n", + " {'height': 300,\n", + " 'url': 'https://i.scdn.co/image/ab67616d00001e024bc66095f8a70bc4e6593f4f',\n", + " 'width': 300},\n", + " {'height': 64,\n", + " 'url': 'https://i.scdn.co/image/ab67616d000048514bc66095f8a70bc4e6593f4f',\n", + " 'width': 64}],\n", + " 'name': 'Future Nostalgia',\n", + " 'release_date': '2020-03-27',\n", + " 'release_date_precision': 'day',\n", + " 'total_tracks': 11,\n", + " 'type': 'album',\n", + " 'uri': 'spotify:album:7fJJK56U9fHixgO0HQkhtI'},\n", + " 'artists': [{'external_urls': {'spotify': 'https://open.spotify.com/artist/6M2wZ9GZgrQXHCFfjv46we'},\n", + " 'href': 'https://api.spotify.com/v1/artists/6M2wZ9GZgrQXHCFfjv46we',\n", + " 'id': '6M2wZ9GZgrQXHCFfjv46we',\n", + " 'name': 'Dua Lipa',\n", + " 'type': 'artist',\n", + " 'uri': 'spotify:artist:6M2wZ9GZgrQXHCFfjv46we'}],\n", + " 'disc_number': 1,\n", + " 'duration_ms': 203807,\n", + " 'explicit': False,\n", + " 'external_ids': {'isrc': 'GBAHT1901299'},\n", + " 'external_urls': {'spotify': 'https://open.spotify.com/track/39LLxExYz6ewLAcYrzQQyP'},\n", + " 'href': 'https://api.spotify.com/v1/tracks/39LLxExYz6ewLAcYrzQQyP',\n", + " 'id': '39LLxExYz6ewLAcYrzQQyP',\n", + " 'is_local': False,\n", + " 'name': 'Levitating',\n", + " 'popularity': 78,\n", + " 'preview_url': 'https://p.scdn.co/mp3-preview/ac28d1b0be285ed3bfd8e9fa5fad133776d7cf36?cid=d7df2abc82674544a78cb3f39fd7d585',\n", + " 'track_number': 5,\n", + " 'type': 'track',\n", + " 'uri': 'spotify:track:39LLxExYz6ewLAcYrzQQyP'}" + ] + }, + "metadata": {}, + "execution_count": 45 + } + ] + }, + { + "cell_type": "code", + "source": [ + "markets = service.get_markets()\n", + "print(len(markets))\n", + "print(markets[0], \"...\", markets[-1])" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "pzBoRiQWJZmo", + "outputId": "8e265cb5-bfe7-4960-999e-a2748862fae5" + }, + "execution_count": 48, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "185\n", + "AD ... ZW\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "result = service.client.audio_features(tracks[0][\"uri\"])\n", + "result" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8pXAYeD2IC36", + "outputId": "4091516c-0348-4b06-daba-4966a86bc194" + }, + "execution_count": 42, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "[{'danceability': 0.695,\n", + " 'energy': 0.884,\n", + " 'key': 6,\n", + " 'loudness': -2.278,\n", + " 'mode': 0,\n", + " 'speechiness': 0.0753,\n", + " 'acousticness': 0.0561,\n", + " 'instrumentalness': 0,\n", + " 'liveness': 0.213,\n", + " 'valence': 0.914,\n", + " 'tempo': 103.014,\n", + " 'type': 'audio_features',\n", + " 'id': '39LLxExYz6ewLAcYrzQQyP',\n", + " 'uri': 'spotify:track:39LLxExYz6ewLAcYrzQQyP',\n", + " 'track_href': 'https://api.spotify.com/v1/tracks/39LLxExYz6ewLAcYrzQQyP',\n", + " 'analysis_url': 'https://api.spotify.com/v1/audio-analysis/39LLxExYz6ewLAcYrzQQyP',\n", + " 'duration_ms': 203808,\n", + " 'time_signature': 4}]" + ] + }, + "metadata": {}, + "execution_count": 42 + } + ] + }, + { + "cell_type": "code", + "source": [ + "#track_ids = [track[\"uri\"] for track in tracks[0:3]]\n", + "\n", + "#result = service.client.audio_features(track_ids)\n", + "#result" + ], + "metadata": { + "collapsed": true, + "id": "yZG2zEWCKZVM" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "from functools import cached_property\n", + "from pandas import DataFrame\n", + "from IPython.core.display import HTML\n", + "\n", + "# all the track-specific display logic\n", + "\n", + "class SpotifyTracks:\n", + "\n", + " def __init__(self, search_term):\n", + " self.query = query\n", + " self.service = SpotifyService()\n", + "\n", + " @cached_property\n", + " def tracks(self):\n", + " return self.service.get_tracks(self.query)\n", + "\n", + " @cached_property\n", + " def records(self):\n", + " records = []\n", + " for track in self.tracks:\n", + " record = {\n", + " \"uri\": track[\"uri\"],\n", + " \"name\": track['name'],\n", + " \"artist\": track[\"artists\"][0][\"name\"],\n", + " \"duration_ms\": track['duration_ms'],\n", + " \"explicit\": track['explicit'],\n", + " \"popularity\": track[\"popularity\"],\n", + " \"album_img_url\": track[\"album\"][\"images\"][0][\"url\"],\n", + " \"preview_url\": track[\"preview_url\"],\n", + " }\n", + " records.append(record)\n", + " return records\n", + "\n", + " @cached_property\n", + " def df(self):\n", + " df = DataFrame(self.records)\n", + " df[\"preview_html\"] = df[\"preview_url\"].apply(self.preview_html)\n", + " df[\"album_art\"] = df[\"album_img_url\"].apply(self.img_html)\n", + " df.drop(columns=[\"album_img_url\", \"preview_url\"], inplace=True)\n", + " return df\n", + "\n", + " @cached_property\n", + " def html_table(self):\n", + " return HTML(self.df.to_html(escape=False, index=False, formatters=dict(Icon=self.img_html)))\n", + "\n", + " def display_table(self):\n", + " display(self.html_table)\n", + "\n", + " @staticmethod\n", + " def img_html(url):\n", + " return ''\n", + "\n", + " @staticmethod\n", + " def preview_html(url):\n", + " if url:\n", + " return 'Listen on Spotify'\n", + " else:\n", + " return None\n", + "\n", + "\n", + "tracks = SpotifyTracks(\"Dua Lipa\")\n", + "tracks.display_table()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1000 + }, + "id": "_D3AoNVOCoT9", + "outputId": "f9aeb9f3-b9ff-4820-b861-ea81864a7320" + }, + "execution_count": 51, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "" + ], + "text/html": [ + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
urinameartistduration_msexplicitpopularitypreview_htmlalbum_art
spotify:track:39LLxExYz6ewLAcYrzQQyPLevitatingDua Lipa203807False78Listen on Spotify
spotify:track:2ekn2ttSfGqwhhate0LSR0New RulesDua Lipa209320False81Listen on Spotify
spotify:track:1vYXt7VSjH9JIM5oRRo7vADance The Night - From Barbie The AlbumDua Lipa176579False84Listen on Spotify
spotify:track:11C4y2Yz1XbHmaQwO06s9fDance The NightDua Lipa176579False72Listen on Spotify
spotify:track:0LnS7aOdOdI1dNKZqdOLz4Dua LipaJack Harlow135053True62Listen on Spotify
spotify:track:3PfIrDoz19wz7qK7tYeu62Don't Start NowDua Lipa183290False84Listen on Spotify
spotify:track:7ef4DlsgrMEH11cDZd32M6One Kiss (with Dua Lipa)Calvin Harris214846False85Listen on Spotify
spotify:track:4OMJGnvZfDvsePyCwRGO7XHoudiniDua Lipa185917False80Listen on Spotify
spotify:track:76cy1WJvNGJTj78UqeA5zrIDGAFDua Lipa217946True77Listen on Spotify
spotify:track:5nujrmhLynf4yMoMtj8AQFLevitating (feat. DaBaby)Dua Lipa203064False82Listen on Spotify
spotify:track:3AzjcOeAmA57TIOr9zF1ZWPhysicalDua Lipa193829False77Listen on Spotify
spotify:track:017PF4Q3l4DBUiWoXk4OWTBreak My HeartDua Lipa221820False77Listen on Spotify
spotify:track:5b5cPscqVEMChvDqscVw26Training SeasonDua Lipa209487False83Listen on Spotify
spotify:track:4rPkN1FMzQyFNP9cLUGIIBLove AgainDua Lipa258004False72Listen on Spotify
spotify:track:6D8y7Bck8h11byRY88Pt2zHoudiniDua Lipa185917False82Listen on Spotify
spotify:track:1diS6nkxMQc3wwC4G1j0bhWe're GoodDua Lipa165506False69Listen on Spotify
spotify:track:59xD5osEFsaNt5PXfIKUnXIllusionDua Lipa188143False79Listen on Spotify
spotify:track:6Qb7YsAqH4wWFUMbGsCpapTraining SeasonDua Lipa209487False81Listen on Spotify
spotify:track:5q0EXnBYyeCdXD72FzJxH0IllusionDua Lipa188143False80Listen on Spotify
spotify:track:2nMOodYNHBAQ3Kc1QNimZUCoolDua Lipa209583False58Listen on Spotify
" + ] + }, + "metadata": {} + } + ] + }, + { + "cell_type": "code", + "source": [ + "tracks.df.head()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 469 + }, + "id": "hvEAYgucL8cp", + "outputId": "165fb367-7444-4ffb-be26-afd62792e05b" + }, + "execution_count": 53, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " uri \\\n", + "0 spotify:track:39LLxExYz6ewLAcYrzQQyP \n", + "1 spotify:track:2ekn2ttSfGqwhhate0LSR0 \n", + "2 spotify:track:1vYXt7VSjH9JIM5oRRo7vA \n", + "3 spotify:track:11C4y2Yz1XbHmaQwO06s9f \n", + "4 spotify:track:0LnS7aOdOdI1dNKZqdOLz4 \n", + "\n", + " name artist duration_ms \\\n", + "0 Levitating Dua Lipa 203807 \n", + "1 New Rules Dua Lipa 209320 \n", + "2 Dance The Night - From Barbie The Album Dua Lipa 176579 \n", + "3 Dance The Night Dua Lipa 176579 \n", + "4 Dua Lipa Jack Harlow 135053 \n", + "\n", + " explicit popularity preview_html \\\n", + "0 False 78 \n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
urinameartistduration_msexplicitpopularitypreview_htmlalbum_art
0spotify:track:39LLxExYz6ewLAcYrzQQyPLevitatingDua Lipa203807False78<a href=\"https://p.scdn.co/mp3-preview/ac28d1b...<img src=\"https://i.scdn.co/image/ab67616d0000...
1spotify:track:2ekn2ttSfGqwhhate0LSR0New RulesDua Lipa209320False81<a href=\"https://p.scdn.co/mp3-preview/e4f2ca2...<img src=\"https://i.scdn.co/image/ab67616d0000...
2spotify:track:1vYXt7VSjH9JIM5oRRo7vADance The Night - From Barbie The AlbumDua Lipa176579False84<a href=\"https://p.scdn.co/mp3-preview/acaea04...<img src=\"https://i.scdn.co/image/ab67616d0000...
3spotify:track:11C4y2Yz1XbHmaQwO06s9fDance The NightDua Lipa176579False72<a href=\"https://p.scdn.co/mp3-preview/acaea04...<img src=\"https://i.scdn.co/image/ab67616d0000...
4spotify:track:0LnS7aOdOdI1dNKZqdOLz4Dua LipaJack Harlow135053True62<a href=\"https://p.scdn.co/mp3-preview/a67e101...<img src=\"https://i.scdn.co/image/ab67616d0000...
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe", + "summary": "{\n \"name\": \"tracks\",\n \"rows\": 5,\n \"fields\": [\n {\n \"column\": \"uri\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"spotify:track:2ekn2ttSfGqwhhate0LSR0\",\n \"spotify:track:0LnS7aOdOdI1dNKZqdOLz4\",\n \"spotify:track:1vYXt7VSjH9JIM5oRRo7vA\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"name\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"New Rules\",\n \"Dua Lipa\",\n \"Dance The Night - From Barbie The Album\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"artist\",\n \"properties\": {\n \"dtype\": \"category\",\n \"num_unique_values\": 2,\n \"samples\": [\n \"Jack Harlow\",\n \"Dua Lipa\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"duration_ms\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 29452,\n \"min\": 135053,\n \"max\": 209320,\n \"num_unique_values\": 4,\n \"samples\": [\n 209320,\n 135053\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"explicit\",\n \"properties\": {\n \"dtype\": \"boolean\",\n \"num_unique_values\": 2,\n \"samples\": [\n true,\n false\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"popularity\",\n \"properties\": {\n \"dtype\": \"number\",\n \"std\": 8,\n \"min\": 62,\n \"max\": 84,\n \"num_unique_values\": 5,\n \"samples\": [\n 81,\n 62\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"preview_html\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 4,\n \"samples\": [\n \"
Listen on Spotify\",\n \"Listen on Spotify\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n },\n {\n \"column\": \"album_art\",\n \"properties\": {\n \"dtype\": \"string\",\n \"num_unique_values\": 5,\n \"samples\": [\n \"\",\n \"\"\n ],\n \"semantic_type\": \"\",\n \"description\": \"\"\n }\n }\n ]\n}" + } + }, + "metadata": {}, + "execution_count": 53 + } + ] + }, + { + "cell_type": "code", + "source": [ + "from functools import cached_property\n", + "from pandas import DataFrame, merge\n", + "\n", + "class SpotifyAudioFeatures(SpotifyTracks):\n", + "\n", + " def __init__(self, search_term):\n", + " super().__init__(search_term)\n", + "\n", + " @cached_property\n", + " def track_ids(self):\n", + " return self.df[\"uri\"].tolist()\n", + "\n", + " @cached_property\n", + " def audio_features(self):\n", + " return self.service.get_audio_features(self.track_ids)\n", + "\n", + " @cached_property\n", + " def audio_features_df(self):\n", + " return DataFrame(self.audio_features)\n", + "\n", + " @cached_property\n", + " def merged_df(self):\n", + " df = merge(self.df.copy(), self.audio_features_df.copy())\n", + " return df\n", + "\n", + "\n", + "SpotifyAudioFeatures(\"Dua Lipa\").merged_df.head()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 515 + }, + "id": "eT4Dw1bCK7iv", + "outputId": "bfcad44c-582e-44f1-ad5b-554a1b094cea" + }, + "execution_count": 57, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " uri \\\n", + "0 spotify:track:2ekn2ttSfGqwhhate0LSR0 \n", + "1 spotify:track:1vYXt7VSjH9JIM5oRRo7vA \n", + "2 spotify:track:11C4y2Yz1XbHmaQwO06s9f \n", + "3 spotify:track:0LnS7aOdOdI1dNKZqdOLz4 \n", + "4 spotify:track:3PfIrDoz19wz7qK7tYeu62 \n", + "\n", + " name artist duration_ms \\\n", + "0 New Rules Dua Lipa 209320 \n", + "1 Dance The Night - From Barbie The Album Dua Lipa 176579 \n", + "2 Dance The Night Dua Lipa 176579 \n", + "3 Dua Lipa Jack Harlow 135053 \n", + "4 Don't Start Now Dua Lipa 183290 \n", + "\n", + " explicit popularity preview_html \\\n", + "0 False 81 \n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
urinameartistduration_msexplicitpopularitypreview_htmlalbum_artdanceabilityenergy...acousticnessinstrumentalnesslivenessvalencetempotypeidtrack_hrefanalysis_urltime_signature
0spotify:track:2ekn2ttSfGqwhhate0LSR0New RulesDua Lipa209320False81<a href=\"https://p.scdn.co/mp3-preview/e4f2ca2...<img src=\"https://i.scdn.co/image/ab67616d0000...0.7620.700...0.002610.0000160.15300.608116.073audio_features2ekn2ttSfGqwhhate0LSR0https://api.spotify.com/v1/tracks/2ekn2ttSfGqw...https://api.spotify.com/v1/audio-analysis/2ekn...4
1spotify:track:1vYXt7VSjH9JIM5oRRo7vADance The Night - From Barbie The AlbumDua Lipa176579False84<a href=\"https://p.scdn.co/mp3-preview/acaea04...<img src=\"https://i.scdn.co/image/ab67616d0000...0.6710.845...0.020700.0000000.32900.775110.056audio_features1vYXt7VSjH9JIM5oRRo7vAhttps://api.spotify.com/v1/tracks/1vYXt7VSjH9J...https://api.spotify.com/v1/audio-analysis/1vYX...4
2spotify:track:11C4y2Yz1XbHmaQwO06s9fDance The NightDua Lipa176579False72<a href=\"https://p.scdn.co/mp3-preview/acaea04...<img src=\"https://i.scdn.co/image/ab67616d0000...0.6710.845...0.020700.0000000.32900.775110.056audio_features11C4y2Yz1XbHmaQwO06s9fhttps://api.spotify.com/v1/tracks/11C4y2Yz1XbH...https://api.spotify.com/v1/audio-analysis/11C4...4
3spotify:track:0LnS7aOdOdI1dNKZqdOLz4Dua LipaJack Harlow135053True62<a href=\"https://p.scdn.co/mp3-preview/a67e101...<img src=\"https://i.scdn.co/image/ab67616d0000...0.8330.652...0.001610.0985000.10600.405158.022audio_features0LnS7aOdOdI1dNKZqdOLz4https://api.spotify.com/v1/tracks/0LnS7aOdOdI1...https://api.spotify.com/v1/audio-analysis/0LnS...4
4spotify:track:3PfIrDoz19wz7qK7tYeu62Don't Start NowDua Lipa183290False84<a href=\"https://p.scdn.co/mp3-preview/cfc6684...<img src=\"https://i.scdn.co/image/ab67616d0000...0.7930.793...0.012300.0000000.09510.679123.950audio_features3PfIrDoz19wz7qK7tYeu62https://api.spotify.com/v1/tracks/3PfIrDoz19wz...https://api.spotify.com/v1/audio-analysis/3PfI...4
\n", + "

5 rows × 24 columns

\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + " \n" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "dataframe" + } + }, + "metadata": {}, + "execution_count": 57 + } + ] + }, + { + "cell_type": "code", + "source": [], + "metadata": { + "id": "3ZFS-FFUMXRk" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file From 0bea266c479785bd1039299ada586e88e63eb3e0 Mon Sep 17 00:00:00 2001 From: MJ Rossetti Date: Sun, 8 Sep 2024 21:53:15 -0400 Subject: [PATCH 2/2] Upload file via web form --- app/drive_service.py | 51 ++++++++------ web_app/__init__.py | 2 + web_app/routes/drive_routes.py | 68 +++++++++++++++++++ web_app/templates/bootstrap_5_layout.html | 1 + .../deliverable_submission_form.html | 39 +++++++++++ 5 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 web_app/routes/drive_routes.py create mode 100644 web_app/templates/deliverable_submission_form.html diff --git a/app/drive_service.py b/app/drive_service.py index 9c43b30..c51f048 100644 --- a/app/drive_service.py +++ b/app/drive_service.py @@ -5,33 +5,42 @@ from app.google_apis import GOOGLE_CREDENTIALS_FILEPATH +def get_folder_id(course_id, assignment_id): + # todo: lookup folder id given it's course id and assignment id + return '1OIPp3BkjUwMZSpKMn_PW9RozdpmRqBno' -def upload_file_to_drive(file_path, folder_id, credentials_json=GOOGLE_CREDENTIALS_FILEPATH): - """ - Upload a file to Google Drive. - - :param credentials_json: Path to the credentials JSON file. - :param file_path: Path to the local file to be uploaded. - :param folder_id: (Optional) Google Drive folder ID where the file will be uploaded. - If not provided, the file will be uploaded to the root directory. - :return: The file ID of the uploaded file. - """ +def get_drive_service(credentials_json=GOOGLE_CREDENTIALS_FILEPATH): # need to enable the google drive api from the google cloud console first credentials = service_account.Credentials.from_service_account_file( - credentials_json, scopes=['https://www.googleapis.com/auth/drive.file'] + credentials_json, + scopes=['https://www.googleapis.com/auth/drive.file'] ) - # Build the Drive API service drive_service = build('drive', 'v3', credentials=credentials) + return drive_service - # Define the file metadata - file_metadata = {'name': file_path.split('/')[-1]} # # Extracts the file name from the path + +def upload_file_to_drive(file_path, folder_id=None): + """ + Upload a file to Google Drive. + + Params: + file_path (str): Path to the local file to be uploaded. + + folder_id (str, optional): Google Drive folder ID where the file will be uploaded. + If not provided, the file will be uploaded to the root directory. + + Returns: + str: The file ID of the uploaded file. + """ + + drive_service = get_drive_service() + + file_metadata = {'name': file_path.split('/')[-1]} if folder_id: file_metadata['parents'] = [folder_id] - breakpoint() - media = MediaFileUpload(file_path, resumable=True) #uploaded_file = drive_service.files().create(body=file_metadata, media_body=media, fields='id').execute() @@ -40,14 +49,14 @@ def upload_file_to_drive(file_path, folder_id, credentials_json=GOOGLE_CREDENTIA fields = ", ".join(["id", "name", "originalFilename", "parents", "quotaBytesUsed", "size", "webContentLink", "webViewLink"]) uploaded_file = drive_service.files().create(body=file_metadata, media_body=media, fields=fields).execute() - # Return the file ID - return uploaded_file.get('id') + return uploaded_file #> {} + -# Example usage if __name__ == '__main__': import os + from pprint import pprint filepath = os.path.join(os.path.dirname(__file__), "..", "test", "notebooks", "Spotify_API_Demo_(Summer_2024).ipynb") assert os.path.isfile(filepath) @@ -55,5 +64,5 @@ def upload_file_to_drive(file_path, folder_id, credentials_json=GOOGLE_CREDENTIA # need to share folder > editor access with service account email address folder_id = '1OIPp3BkjUwMZSpKMn_PW9RozdpmRqBno' - file_id = upload_file_to_drive(filepath=filepath, folder_id=folder_id) - print(f"File uploaded successfully with ID: {file_id}") + uploaded_file = upload_file_to_drive(filepath=filepath, folder_id=folder_id) + pprint(uploaded_file) diff --git a/web_app/__init__.py b/web_app/__init__.py index cc2a7cf..fb2a0c4 100644 --- a/web_app/__init__.py +++ b/web_app/__init__.py @@ -15,6 +15,7 @@ from web_app.routes.user_routes import user_routes from web_app.routes.product_routes import product_routes from web_app.routes.order_routes import order_routes +from web_app.routes.drive_routes import drive_routes load_dotenv() @@ -70,6 +71,7 @@ def create_app(): app.register_blueprint(user_routes) app.register_blueprint(product_routes) app.register_blueprint(order_routes) + app.register_blueprint(drive_routes) return app diff --git a/web_app/routes/drive_routes.py b/web_app/routes/drive_routes.py new file mode 100644 index 0000000..02415a6 --- /dev/null +++ b/web_app/routes/drive_routes.py @@ -0,0 +1,68 @@ + +from flask import Blueprint, render_template, request, flash, redirect, session #, current_app + +from io import BytesIO +from googleapiclient.http import MediaIoBaseUpload #MediaFileUpload + +from app.drive_service import get_folder_id, get_drive_service + + +drive_routes = Blueprint("drive_routes", __name__) + + +@drive_routes.route("/submissions/form") +def submission_form(): + return render_template("deliverable_submission_form.html") + + +@drive_routes.route("/submissions/upload", methods=["POST"]) +def submission_upload(): + form_data = dict(request.form) + print("FORM DATA:", form_data) + print("FILES:", request.files) + + try: + course_name = form_data["course_name"] + assignment_name = form_data["assignment_name"] + + selected_file = request.files["selected_file"] + filename = selected_file.filename + mimetype = selected_file.mimetype + print(selected_file) + print(selected_file.filename) + print(selected_file.mimetype) + print(selected_file.content_type) + print(selected_file.content_length) + #> + + file_content = BytesIO(selected_file.read()) + print(type(file_content)) + media = MediaIoBaseUpload(file_content, mimetype=mimetype, resumable=True) + print("MEDIA:", media) + + # USER INFO + current_user = session.get("current_user") + email_address = current_user["email"] + + # UPLOAD TO DRIVE + + drive_service = get_drive_service() + + folder_id = get_folder_id(course_name, assignment_name) + #net_id = email_address.split("@")[0] + #filename = filename.lower().split(".ipynb")[0] + " - " + net_id + ".ipynb" + #> 'rock_paper_scissors_(spring_2024)_solutions - first.last.ipynb' + file_metadata = { + 'name': filename, + 'parents': [folder_id], + } + print("METADATA:", file_metadata) + + drive_service.files().create(body=file_metadata, media_body=media, fields='id').execute() + + flash("File Uploaded Successfully", "success") + return redirect("/submissions/form") + except Exception as err: + print("OOPS", err) + flash("OOPS, Something went wrong. Please try again.", "warning") + return redirect("/submissions/form") diff --git a/web_app/templates/bootstrap_5_layout.html b/web_app/templates/bootstrap_5_layout.html index 4ba1dd1..66200d6 100644 --- a/web_app/templates/bootstrap_5_layout.html +++ b/web_app/templates/bootstrap_5_layout.html @@ -81,6 +81,7 @@ {% set protected_nav = [ ('/about', 'about', 'About'), ('/products', 'products', 'Products'), + ('/submissions/form', 'deliverable_submission_form', 'Deliverable Submission Form'), ('/user/orders', 'user_orders', 'Orders'), ] -%} diff --git a/web_app/templates/deliverable_submission_form.html b/web_app/templates/deliverable_submission_form.html new file mode 100644 index 0000000..ddb9628 --- /dev/null +++ b/web_app/templates/deliverable_submission_form.html @@ -0,0 +1,39 @@ +{% extends "bootstrap_5_layout.html" %} +{% set page_title = "Deliverable Submission Form" %} +{% set active_page = "submission_form" %} + +{% block content %} + +

Deliverable Submission Form

+ +
+ + + + + +
+ + + +
+ + +
+ + +
+ +{% endblock %}