We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
playlist list causing UnboundLocalError
### What Red version are you using?

3.4.14

### Cog name

Audio

### Command name

playlist list

### What did you expect to happen?

I first created an empty playlist, and then called `playlist list`, where I expected Red to show me the empty playlist.

### What actually happened?

Red confirmed that it created the empty playlist, but reported that no playlists were found.

### How can we reproduce this error?

1. `[p] playlist create playlistName`
2. `[p] playlist list`


### Anything else?

I found an error in my log files, an `UnboundLocalError` as referenced in the title.
![image](https://user-images.githubusercontent.com/92138376/136501198-5cb5d5c2-140c-4931-b954-ff18af7052ec.png)
Additionally, here is the output of `[p] debuginfo`.
<img width="1112" alt="image" src="https://user-images.githubusercontent.com/92138376/136501321-a604d42d-e837-4bab-8bcc-4c89ba5b7cb5.png">


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `redbot/cogs/audio/apis/playlist_wrapper.py`
Content:
```
1 import concurrent
2 import json
3 import logging
4 from pathlib import Path
5 
6 from types import SimpleNamespace
7 from typing import List, MutableMapping, Optional
8 
9 from redbot.core import Config
10 from redbot.core.bot import Red
11 from redbot.core.i18n import Translator
12 from redbot.core.utils import AsyncIter
13 from redbot.core.utils.dbtools import APSWConnectionWrapper
14 
15 from ..audio_logging import debug_exc_log
16 from ..sql_statements import (
17     HANDLE_DISCORD_DATA_DELETION_QUERY,
18     PLAYLIST_CREATE_INDEX,
19     PLAYLIST_CREATE_TABLE,
20     PLAYLIST_DELETE,
21     PLAYLIST_DELETE_SCHEDULED,
22     PLAYLIST_DELETE_SCOPE,
23     PLAYLIST_FETCH,
24     PLAYLIST_FETCH_ALL,
25     PLAYLIST_FETCH_ALL_CONVERTER,
26     PLAYLIST_FETCH_ALL_WITH_FILTER,
27     PLAYLIST_UPSERT,
28     PRAGMA_FETCH_user_version,
29     PRAGMA_SET_journal_mode,
30     PRAGMA_SET_read_uncommitted,
31     PRAGMA_SET_temp_store,
32     PRAGMA_SET_user_version,
33 )
34 from ..utils import PlaylistScope
35 from .api_utils import PlaylistFetchResult
36 
37 log = logging.getLogger("red.cogs.Audio.api.Playlists")
38 _ = Translator("Audio", Path(__file__))
39 
40 
41 class PlaylistWrapper:
42     def __init__(self, bot: Red, config: Config, conn: APSWConnectionWrapper):
43         self.bot = bot
44         self.database = conn
45         self.config = config
46         self.statement = SimpleNamespace()
47         self.statement.pragma_temp_store = PRAGMA_SET_temp_store
48         self.statement.pragma_journal_mode = PRAGMA_SET_journal_mode
49         self.statement.pragma_read_uncommitted = PRAGMA_SET_read_uncommitted
50         self.statement.set_user_version = PRAGMA_SET_user_version
51         self.statement.get_user_version = PRAGMA_FETCH_user_version
52         self.statement.create_table = PLAYLIST_CREATE_TABLE
53         self.statement.create_index = PLAYLIST_CREATE_INDEX
54 
55         self.statement.upsert = PLAYLIST_UPSERT
56         self.statement.delete = PLAYLIST_DELETE
57         self.statement.delete_scope = PLAYLIST_DELETE_SCOPE
58         self.statement.delete_scheduled = PLAYLIST_DELETE_SCHEDULED
59 
60         self.statement.get_one = PLAYLIST_FETCH
61         self.statement.get_all = PLAYLIST_FETCH_ALL
62         self.statement.get_all_with_filter = PLAYLIST_FETCH_ALL_WITH_FILTER
63         self.statement.get_all_converter = PLAYLIST_FETCH_ALL_CONVERTER
64 
65         self.statement.drop_user_playlists = HANDLE_DISCORD_DATA_DELETION_QUERY
66 
67     async def init(self) -> None:
68         """Initialize the Playlist table."""
69         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
70             executor.submit(self.database.cursor().execute, self.statement.pragma_temp_store)
71             executor.submit(self.database.cursor().execute, self.statement.pragma_journal_mode)
72             executor.submit(self.database.cursor().execute, self.statement.pragma_read_uncommitted)
73             executor.submit(self.database.cursor().execute, self.statement.create_table)
74             executor.submit(self.database.cursor().execute, self.statement.create_index)
75 
76     @staticmethod
77     def get_scope_type(scope: str) -> int:
78         """Convert a scope to a numerical identifier."""
79         if scope == PlaylistScope.GLOBAL.value:
80             table = 1
81         elif scope == PlaylistScope.USER.value:
82             table = 3
83         else:
84             table = 2
85         return table
86 
87     async def fetch(self, scope: str, playlist_id: int, scope_id: int) -> PlaylistFetchResult:
88         """Fetch a single playlist."""
89         scope_type = self.get_scope_type(scope)
90 
91         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
92             for future in concurrent.futures.as_completed(
93                 [
94                     executor.submit(
95                         self.database.cursor().execute,
96                         self.statement.get_one,
97                         (
98                             {
99                                 "playlist_id": playlist_id,
100                                 "scope_id": scope_id,
101                                 "scope_type": scope_type,
102                             }
103                         ),
104                     )
105                 ]
106             ):
107                 try:
108                     row_result = future.result()
109                 except Exception as exc:
110                     debug_exc_log(log, exc, "Failed to completed playlist fetch from database")
111             row = row_result.fetchone()
112             if row:
113                 row = PlaylistFetchResult(*row)
114         return row
115 
116     async def fetch_all(
117         self, scope: str, scope_id: int, author_id=None
118     ) -> List[PlaylistFetchResult]:
119         """Fetch all playlists."""
120         scope_type = self.get_scope_type(scope)
121         output = []
122         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
123             if author_id is not None:
124                 for future in concurrent.futures.as_completed(
125                     [
126                         executor.submit(
127                             self.database.cursor().execute,
128                             self.statement.get_all_with_filter,
129                             (
130                                 {
131                                     "scope_type": scope_type,
132                                     "scope_id": scope_id,
133                                     "author_id": author_id,
134                                 }
135                             ),
136                         )
137                     ]
138                 ):
139                     try:
140                         row_result = future.result()
141                     except Exception as exc:
142                         debug_exc_log(log, exc, "Failed to completed playlist fetch from database")
143                         return []
144             else:
145                 for future in concurrent.futures.as_completed(
146                     [
147                         executor.submit(
148                             self.database.cursor().execute,
149                             self.statement.get_all,
150                             ({"scope_type": scope_type, "scope_id": scope_id}),
151                         )
152                     ]
153                 ):
154                     try:
155                         row_result = future.result()
156                     except Exception as exc:
157                         debug_exc_log(log, exc, "Failed to completed playlist fetch from database")
158                         return []
159         async for row in AsyncIter(row_result):
160             output.append(PlaylistFetchResult(*row))
161         return output
162 
163     async def fetch_all_converter(
164         self, scope: str, playlist_name, playlist_id
165     ) -> List[PlaylistFetchResult]:
166         """Fetch all playlists with the specified filter."""
167         scope_type = self.get_scope_type(scope)
168         try:
169             playlist_id = int(playlist_id)
170         except Exception as exc:
171             debug_exc_log(log, exc, "Failed converting playlist_id to int")
172             playlist_id = -1
173 
174         output = []
175         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
176             for future in concurrent.futures.as_completed(
177                 [
178                     executor.submit(
179                         self.database.cursor().execute,
180                         self.statement.get_all_converter,
181                         (
182                             {
183                                 "scope_type": scope_type,
184                                 "playlist_name": playlist_name,
185                                 "playlist_id": playlist_id,
186                             }
187                         ),
188                     )
189                 ]
190             ):
191                 try:
192                     row_result = future.result()
193                 except Exception as exc:
194                     debug_exc_log(log, exc, "Failed to completed fetch from database")
195 
196             async for row in AsyncIter(row_result):
197                 output.append(PlaylistFetchResult(*row))
198         return output
199 
200     async def delete(self, scope: str, playlist_id: int, scope_id: int):
201         """Deletes a single playlists."""
202         scope_type = self.get_scope_type(scope)
203         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
204             executor.submit(
205                 self.database.cursor().execute,
206                 self.statement.delete,
207                 ({"playlist_id": playlist_id, "scope_id": scope_id, "scope_type": scope_type}),
208             )
209 
210     async def delete_scheduled(self):
211         """Clean up database from all deleted playlists."""
212         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
213             executor.submit(self.database.cursor().execute, self.statement.delete_scheduled)
214 
215     async def drop(self, scope: str):
216         """Delete all playlists in a scope."""
217         scope_type = self.get_scope_type(scope)
218         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
219             executor.submit(
220                 self.database.cursor().execute,
221                 self.statement.delete_scope,
222                 ({"scope_type": scope_type}),
223             )
224 
225     async def create_table(self):
226         """Create the playlist table."""
227         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
228             executor.submit(self.database.cursor().execute, PLAYLIST_CREATE_TABLE)
229 
230     async def upsert(
231         self,
232         scope: str,
233         playlist_id: int,
234         playlist_name: str,
235         scope_id: int,
236         author_id: int,
237         playlist_url: Optional[str],
238         tracks: List[MutableMapping],
239     ):
240         """Insert or update a playlist into the database."""
241         scope_type = self.get_scope_type(scope)
242         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
243             executor.submit(
244                 self.database.cursor().execute,
245                 self.statement.upsert,
246                 {
247                     "scope_type": str(scope_type),
248                     "playlist_id": int(playlist_id),
249                     "playlist_name": str(playlist_name),
250                     "scope_id": int(scope_id),
251                     "author_id": int(author_id),
252                     "playlist_url": playlist_url,
253                     "tracks": json.dumps(tracks),
254                 },
255             )
256 
257     async def handle_playlist_user_id_deletion(self, user_id: int):
258         with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
259             executor.submit(
260                 self.database.cursor().execute,
261                 self.statement.drop_user_playlists,
262                 {"user_id": user_id},
263             )
264
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/redbot/cogs/audio/apis/playlist_wrapper.py b/redbot/cogs/audio/apis/playlist_wrapper.py
--- a/redbot/cogs/audio/apis/playlist_wrapper.py
+++ b/redbot/cogs/audio/apis/playlist_wrapper.py
@@ -84,7 +84,9 @@
             table = 2
         return table
 
-    async def fetch(self, scope: str, playlist_id: int, scope_id: int) -> PlaylistFetchResult:
+    async def fetch(
+        self, scope: str, playlist_id: int, scope_id: int
+    ) -> Optional[PlaylistFetchResult]:
         """Fetch a single playlist."""
         scope_type = self.get_scope_type(scope)
 
@@ -107,7 +109,8 @@
                 try:
                     row_result = future.result()
                 except Exception as exc:
-                    debug_exc_log(log, exc, "Failed to completed playlist fetch from database")
+                    debug_exc_log(log, exc, "Failed to complete playlist fetch from database")
+                    return None
             row = row_result.fetchone()
             if row:
                 row = PlaylistFetchResult(*row)
@@ -139,7 +142,7 @@
                     try:
                         row_result = future.result()
                     except Exception as exc:
-                        debug_exc_log(log, exc, "Failed to completed playlist fetch from database")
+                        debug_exc_log(log, exc, "Failed to complete playlist fetch from database")
                         return []
             else:
                 for future in concurrent.futures.as_completed(
@@ -154,7 +157,7 @@
                     try:
                         row_result = future.result()
                     except Exception as exc:
-                        debug_exc_log(log, exc, "Failed to completed playlist fetch from database")
+                        debug_exc_log(log, exc, "Failed to complete playlist fetch from database")
                         return []
         async for row in AsyncIter(row_result):
             output.append(PlaylistFetchResult(*row))
@@ -191,7 +194,8 @@
                 try:
                     row_result = future.result()
                 except Exception as exc:
-                    debug_exc_log(log, exc, "Failed to completed fetch from database")
+                    debug_exc_log(log, exc, "Failed to complete fetch from database")
+                    return []
 
             async for row in AsyncIter(row_result):
                 output.append(PlaylistFetchResult(*row))

{"golden_diff": "diff --git a/redbot/cogs/audio/apis/playlist_wrapper.py b/redbot/cogs/audio/apis/playlist_wrapper.py\n--- a/redbot/cogs/audio/apis/playlist_wrapper.py\n+++ b/redbot/cogs/audio/apis/playlist_wrapper.py\n@@ -84,7 +84,9 @@\n             table = 2\n         return table\n \n-    async def fetch(self, scope: str, playlist_id: int, scope_id: int) -> PlaylistFetchResult:\n+    async def fetch(\n+        self, scope: str, playlist_id: int, scope_id: int\n+    ) -> Optional[PlaylistFetchResult]:\n         \"\"\"Fetch a single playlist.\"\"\"\n         scope_type = self.get_scope_type(scope)\n \n@@ -107,7 +109,8 @@\n                 try:\n                     row_result = future.result()\n                 except Exception as exc:\n-                    debug_exc_log(log, exc, \"Failed to completed playlist fetch from database\")\n+                    debug_exc_log(log, exc, \"Failed to complete playlist fetch from database\")\n+                    return None\n             row = row_result.fetchone()\n             if row:\n                 row = PlaylistFetchResult(*row)\n@@ -139,7 +142,7 @@\n                     try:\n                         row_result = future.result()\n                     except Exception as exc:\n-                        debug_exc_log(log, exc, \"Failed to completed playlist fetch from database\")\n+                        debug_exc_log(log, exc, \"Failed to complete playlist fetch from database\")\n                         return []\n             else:\n                 for future in concurrent.futures.as_completed(\n@@ -154,7 +157,7 @@\n                     try:\n                         row_result = future.result()\n                     except Exception as exc:\n-                        debug_exc_log(log, exc, \"Failed to completed playlist fetch from database\")\n+                        debug_exc_log(log, exc, \"Failed to complete playlist fetch from database\")\n                         return []\n         async for row in AsyncIter(row_result):\n             output.append(PlaylistFetchResult(*row))\n@@ -191,7 +194,8 @@\n                 try:\n                     row_result = future.result()\n                 except Exception as exc:\n-                    debug_exc_log(log, exc, \"Failed to completed fetch from database\")\n+                    debug_exc_log(log, exc, \"Failed to complete fetch from database\")\n+                    return []\n \n             async for row in AsyncIter(row_result):\n                 output.append(PlaylistFetchResult(*row))\n", "issue": "playlist list causing UnboundLocalError\n### What Red version are you using?\n\n3.4.14\n\n### Cog name\n\nAudio\n\n### Command name\n\nplaylist list\n\n### What did you expect to happen?\n\nI first created an empty playlist, and then called `playlist list`, where I expected Red to show me the empty playlist.\n\n### What actually happened?\n\nRed confirmed that it created the empty playlist, but reported that no playlists were found.\n\n### How can we reproduce this error?\n\n1. `[p] playlist create playlistName`\r\n2. `[p] playlist list`\r\n\n\n### Anything else?\n\nI found an error in my log files, an `UnboundLocalError` as referenced in the title.\r\n![image](https://user-images.githubusercontent.com/92138376/136501198-5cb5d5c2-140c-4931-b954-ff18af7052ec.png)\r\nAdditionally, here is the output of `[p] debuginfo`.\r\n<img width=\"1112\" alt=\"image\" src=\"https://user-images.githubusercontent.com/92138376/136501321-a604d42d-e837-4bab-8bcc-4c89ba5b7cb5.png\">\r\n\n", "before_files": [{"content": "import concurrent\nimport json\nimport logging\nfrom pathlib import Path\n\nfrom types import SimpleNamespace\nfrom typing import List, MutableMapping, Optional\n\nfrom redbot.core import Config\nfrom redbot.core.bot import Red\nfrom redbot.core.i18n import Translator\nfrom redbot.core.utils import AsyncIter\nfrom redbot.core.utils.dbtools import APSWConnectionWrapper\n\nfrom ..audio_logging import debug_exc_log\nfrom ..sql_statements import (\n    HANDLE_DISCORD_DATA_DELETION_QUERY,\n    PLAYLIST_CREATE_INDEX,\n    PLAYLIST_CREATE_TABLE,\n    PLAYLIST_DELETE,\n    PLAYLIST_DELETE_SCHEDULED,\n    PLAYLIST_DELETE_SCOPE,\n    PLAYLIST_FETCH,\n    PLAYLIST_FETCH_ALL,\n    PLAYLIST_FETCH_ALL_CONVERTER,\n    PLAYLIST_FETCH_ALL_WITH_FILTER,\n    PLAYLIST_UPSERT,\n    PRAGMA_FETCH_user_version,\n    PRAGMA_SET_journal_mode,\n    PRAGMA_SET_read_uncommitted,\n    PRAGMA_SET_temp_store,\n    PRAGMA_SET_user_version,\n)\nfrom ..utils import PlaylistScope\nfrom .api_utils import PlaylistFetchResult\n\nlog = logging.getLogger(\"red.cogs.Audio.api.Playlists\")\n_ = Translator(\"Audio\", Path(__file__))\n\n\nclass PlaylistWrapper:\n    def __init__(self, bot: Red, config: Config, conn: APSWConnectionWrapper):\n        self.bot = bot\n        self.database = conn\n        self.config = config\n        self.statement = SimpleNamespace()\n        self.statement.pragma_temp_store = PRAGMA_SET_temp_store\n        self.statement.pragma_journal_mode = PRAGMA_SET_journal_mode\n        self.statement.pragma_read_uncommitted = PRAGMA_SET_read_uncommitted\n        self.statement.set_user_version = PRAGMA_SET_user_version\n        self.statement.get_user_version = PRAGMA_FETCH_user_version\n        self.statement.create_table = PLAYLIST_CREATE_TABLE\n        self.statement.create_index = PLAYLIST_CREATE_INDEX\n\n        self.statement.upsert = PLAYLIST_UPSERT\n        self.statement.delete = PLAYLIST_DELETE\n        self.statement.delete_scope = PLAYLIST_DELETE_SCOPE\n        self.statement.delete_scheduled = PLAYLIST_DELETE_SCHEDULED\n\n        self.statement.get_one = PLAYLIST_FETCH\n        self.statement.get_all = PLAYLIST_FETCH_ALL\n        self.statement.get_all_with_filter = PLAYLIST_FETCH_ALL_WITH_FILTER\n        self.statement.get_all_converter = PLAYLIST_FETCH_ALL_CONVERTER\n\n        self.statement.drop_user_playlists = HANDLE_DISCORD_DATA_DELETION_QUERY\n\n    async def init(self) -> None:\n        \"\"\"Initialize the Playlist table.\"\"\"\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(self.database.cursor().execute, self.statement.pragma_temp_store)\n            executor.submit(self.database.cursor().execute, self.statement.pragma_journal_mode)\n            executor.submit(self.database.cursor().execute, self.statement.pragma_read_uncommitted)\n            executor.submit(self.database.cursor().execute, self.statement.create_table)\n            executor.submit(self.database.cursor().execute, self.statement.create_index)\n\n    @staticmethod\n    def get_scope_type(scope: str) -> int:\n        \"\"\"Convert a scope to a numerical identifier.\"\"\"\n        if scope == PlaylistScope.GLOBAL.value:\n            table = 1\n        elif scope == PlaylistScope.USER.value:\n            table = 3\n        else:\n            table = 2\n        return table\n\n    async def fetch(self, scope: str, playlist_id: int, scope_id: int) -> PlaylistFetchResult:\n        \"\"\"Fetch a single playlist.\"\"\"\n        scope_type = self.get_scope_type(scope)\n\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            for future in concurrent.futures.as_completed(\n                [\n                    executor.submit(\n                        self.database.cursor().execute,\n                        self.statement.get_one,\n                        (\n                            {\n                                \"playlist_id\": playlist_id,\n                                \"scope_id\": scope_id,\n                                \"scope_type\": scope_type,\n                            }\n                        ),\n                    )\n                ]\n            ):\n                try:\n                    row_result = future.result()\n                except Exception as exc:\n                    debug_exc_log(log, exc, \"Failed to completed playlist fetch from database\")\n            row = row_result.fetchone()\n            if row:\n                row = PlaylistFetchResult(*row)\n        return row\n\n    async def fetch_all(\n        self, scope: str, scope_id: int, author_id=None\n    ) -> List[PlaylistFetchResult]:\n        \"\"\"Fetch all playlists.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        output = []\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            if author_id is not None:\n                for future in concurrent.futures.as_completed(\n                    [\n                        executor.submit(\n                            self.database.cursor().execute,\n                            self.statement.get_all_with_filter,\n                            (\n                                {\n                                    \"scope_type\": scope_type,\n                                    \"scope_id\": scope_id,\n                                    \"author_id\": author_id,\n                                }\n                            ),\n                        )\n                    ]\n                ):\n                    try:\n                        row_result = future.result()\n                    except Exception as exc:\n                        debug_exc_log(log, exc, \"Failed to completed playlist fetch from database\")\n                        return []\n            else:\n                for future in concurrent.futures.as_completed(\n                    [\n                        executor.submit(\n                            self.database.cursor().execute,\n                            self.statement.get_all,\n                            ({\"scope_type\": scope_type, \"scope_id\": scope_id}),\n                        )\n                    ]\n                ):\n                    try:\n                        row_result = future.result()\n                    except Exception as exc:\n                        debug_exc_log(log, exc, \"Failed to completed playlist fetch from database\")\n                        return []\n        async for row in AsyncIter(row_result):\n            output.append(PlaylistFetchResult(*row))\n        return output\n\n    async def fetch_all_converter(\n        self, scope: str, playlist_name, playlist_id\n    ) -> List[PlaylistFetchResult]:\n        \"\"\"Fetch all playlists with the specified filter.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        try:\n            playlist_id = int(playlist_id)\n        except Exception as exc:\n            debug_exc_log(log, exc, \"Failed converting playlist_id to int\")\n            playlist_id = -1\n\n        output = []\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            for future in concurrent.futures.as_completed(\n                [\n                    executor.submit(\n                        self.database.cursor().execute,\n                        self.statement.get_all_converter,\n                        (\n                            {\n                                \"scope_type\": scope_type,\n                                \"playlist_name\": playlist_name,\n                                \"playlist_id\": playlist_id,\n                            }\n                        ),\n                    )\n                ]\n            ):\n                try:\n                    row_result = future.result()\n                except Exception as exc:\n                    debug_exc_log(log, exc, \"Failed to completed fetch from database\")\n\n            async for row in AsyncIter(row_result):\n                output.append(PlaylistFetchResult(*row))\n        return output\n\n    async def delete(self, scope: str, playlist_id: int, scope_id: int):\n        \"\"\"Deletes a single playlists.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.delete,\n                ({\"playlist_id\": playlist_id, \"scope_id\": scope_id, \"scope_type\": scope_type}),\n            )\n\n    async def delete_scheduled(self):\n        \"\"\"Clean up database from all deleted playlists.\"\"\"\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(self.database.cursor().execute, self.statement.delete_scheduled)\n\n    async def drop(self, scope: str):\n        \"\"\"Delete all playlists in a scope.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.delete_scope,\n                ({\"scope_type\": scope_type}),\n            )\n\n    async def create_table(self):\n        \"\"\"Create the playlist table.\"\"\"\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(self.database.cursor().execute, PLAYLIST_CREATE_TABLE)\n\n    async def upsert(\n        self,\n        scope: str,\n        playlist_id: int,\n        playlist_name: str,\n        scope_id: int,\n        author_id: int,\n        playlist_url: Optional[str],\n        tracks: List[MutableMapping],\n    ):\n        \"\"\"Insert or update a playlist into the database.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.upsert,\n                {\n                    \"scope_type\": str(scope_type),\n                    \"playlist_id\": int(playlist_id),\n                    \"playlist_name\": str(playlist_name),\n                    \"scope_id\": int(scope_id),\n                    \"author_id\": int(author_id),\n                    \"playlist_url\": playlist_url,\n                    \"tracks\": json.dumps(tracks),\n                },\n            )\n\n    async def handle_playlist_user_id_deletion(self, user_id: int):\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.drop_user_playlists,\n                {\"user_id\": user_id},\n            )\n", "path": "redbot/cogs/audio/apis/playlist_wrapper.py"}], "after_files": [{"content": "import concurrent\nimport json\nimport logging\nfrom pathlib import Path\n\nfrom types import SimpleNamespace\nfrom typing import List, MutableMapping, Optional\n\nfrom redbot.core import Config\nfrom redbot.core.bot import Red\nfrom redbot.core.i18n import Translator\nfrom redbot.core.utils import AsyncIter\nfrom redbot.core.utils.dbtools import APSWConnectionWrapper\n\nfrom ..audio_logging import debug_exc_log\nfrom ..sql_statements import (\n    HANDLE_DISCORD_DATA_DELETION_QUERY,\n    PLAYLIST_CREATE_INDEX,\n    PLAYLIST_CREATE_TABLE,\n    PLAYLIST_DELETE,\n    PLAYLIST_DELETE_SCHEDULED,\n    PLAYLIST_DELETE_SCOPE,\n    PLAYLIST_FETCH,\n    PLAYLIST_FETCH_ALL,\n    PLAYLIST_FETCH_ALL_CONVERTER,\n    PLAYLIST_FETCH_ALL_WITH_FILTER,\n    PLAYLIST_UPSERT,\n    PRAGMA_FETCH_user_version,\n    PRAGMA_SET_journal_mode,\n    PRAGMA_SET_read_uncommitted,\n    PRAGMA_SET_temp_store,\n    PRAGMA_SET_user_version,\n)\nfrom ..utils import PlaylistScope\nfrom .api_utils import PlaylistFetchResult\n\nlog = logging.getLogger(\"red.cogs.Audio.api.Playlists\")\n_ = Translator(\"Audio\", Path(__file__))\n\n\nclass PlaylistWrapper:\n    def __init__(self, bot: Red, config: Config, conn: APSWConnectionWrapper):\n        self.bot = bot\n        self.database = conn\n        self.config = config\n        self.statement = SimpleNamespace()\n        self.statement.pragma_temp_store = PRAGMA_SET_temp_store\n        self.statement.pragma_journal_mode = PRAGMA_SET_journal_mode\n        self.statement.pragma_read_uncommitted = PRAGMA_SET_read_uncommitted\n        self.statement.set_user_version = PRAGMA_SET_user_version\n        self.statement.get_user_version = PRAGMA_FETCH_user_version\n        self.statement.create_table = PLAYLIST_CREATE_TABLE\n        self.statement.create_index = PLAYLIST_CREATE_INDEX\n\n        self.statement.upsert = PLAYLIST_UPSERT\n        self.statement.delete = PLAYLIST_DELETE\n        self.statement.delete_scope = PLAYLIST_DELETE_SCOPE\n        self.statement.delete_scheduled = PLAYLIST_DELETE_SCHEDULED\n\n        self.statement.get_one = PLAYLIST_FETCH\n        self.statement.get_all = PLAYLIST_FETCH_ALL\n        self.statement.get_all_with_filter = PLAYLIST_FETCH_ALL_WITH_FILTER\n        self.statement.get_all_converter = PLAYLIST_FETCH_ALL_CONVERTER\n\n        self.statement.drop_user_playlists = HANDLE_DISCORD_DATA_DELETION_QUERY\n\n    async def init(self) -> None:\n        \"\"\"Initialize the Playlist table.\"\"\"\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(self.database.cursor().execute, self.statement.pragma_temp_store)\n            executor.submit(self.database.cursor().execute, self.statement.pragma_journal_mode)\n            executor.submit(self.database.cursor().execute, self.statement.pragma_read_uncommitted)\n            executor.submit(self.database.cursor().execute, self.statement.create_table)\n            executor.submit(self.database.cursor().execute, self.statement.create_index)\n\n    @staticmethod\n    def get_scope_type(scope: str) -> int:\n        \"\"\"Convert a scope to a numerical identifier.\"\"\"\n        if scope == PlaylistScope.GLOBAL.value:\n            table = 1\n        elif scope == PlaylistScope.USER.value:\n            table = 3\n        else:\n            table = 2\n        return table\n\n    async def fetch(\n        self, scope: str, playlist_id: int, scope_id: int\n    ) -> Optional[PlaylistFetchResult]:\n        \"\"\"Fetch a single playlist.\"\"\"\n        scope_type = self.get_scope_type(scope)\n\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            for future in concurrent.futures.as_completed(\n                [\n                    executor.submit(\n                        self.database.cursor().execute,\n                        self.statement.get_one,\n                        (\n                            {\n                                \"playlist_id\": playlist_id,\n                                \"scope_id\": scope_id,\n                                \"scope_type\": scope_type,\n                            }\n                        ),\n                    )\n                ]\n            ):\n                try:\n                    row_result = future.result()\n                except Exception as exc:\n                    debug_exc_log(log, exc, \"Failed to complete playlist fetch from database\")\n                    return None\n            row = row_result.fetchone()\n            if row:\n                row = PlaylistFetchResult(*row)\n        return row\n\n    async def fetch_all(\n        self, scope: str, scope_id: int, author_id=None\n    ) -> List[PlaylistFetchResult]:\n        \"\"\"Fetch all playlists.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        output = []\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            if author_id is not None:\n                for future in concurrent.futures.as_completed(\n                    [\n                        executor.submit(\n                            self.database.cursor().execute,\n                            self.statement.get_all_with_filter,\n                            (\n                                {\n                                    \"scope_type\": scope_type,\n                                    \"scope_id\": scope_id,\n                                    \"author_id\": author_id,\n                                }\n                            ),\n                        )\n                    ]\n                ):\n                    try:\n                        row_result = future.result()\n                    except Exception as exc:\n                        debug_exc_log(log, exc, \"Failed to complete playlist fetch from database\")\n                        return []\n            else:\n                for future in concurrent.futures.as_completed(\n                    [\n                        executor.submit(\n                            self.database.cursor().execute,\n                            self.statement.get_all,\n                            ({\"scope_type\": scope_type, \"scope_id\": scope_id}),\n                        )\n                    ]\n                ):\n                    try:\n                        row_result = future.result()\n                    except Exception as exc:\n                        debug_exc_log(log, exc, \"Failed to complete playlist fetch from database\")\n                        return []\n        async for row in AsyncIter(row_result):\n            output.append(PlaylistFetchResult(*row))\n        return output\n\n    async def fetch_all_converter(\n        self, scope: str, playlist_name, playlist_id\n    ) -> List[PlaylistFetchResult]:\n        \"\"\"Fetch all playlists with the specified filter.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        try:\n            playlist_id = int(playlist_id)\n        except Exception as exc:\n            debug_exc_log(log, exc, \"Failed converting playlist_id to int\")\n            playlist_id = -1\n\n        output = []\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            for future in concurrent.futures.as_completed(\n                [\n                    executor.submit(\n                        self.database.cursor().execute,\n                        self.statement.get_all_converter,\n                        (\n                            {\n                                \"scope_type\": scope_type,\n                                \"playlist_name\": playlist_name,\n                                \"playlist_id\": playlist_id,\n                            }\n                        ),\n                    )\n                ]\n            ):\n                try:\n                    row_result = future.result()\n                except Exception as exc:\n                    debug_exc_log(log, exc, \"Failed to complete fetch from database\")\n                    return []\n\n            async for row in AsyncIter(row_result):\n                output.append(PlaylistFetchResult(*row))\n        return output\n\n    async def delete(self, scope: str, playlist_id: int, scope_id: int):\n        \"\"\"Deletes a single playlists.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.delete,\n                ({\"playlist_id\": playlist_id, \"scope_id\": scope_id, \"scope_type\": scope_type}),\n            )\n\n    async def delete_scheduled(self):\n        \"\"\"Clean up database from all deleted playlists.\"\"\"\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(self.database.cursor().execute, self.statement.delete_scheduled)\n\n    async def drop(self, scope: str):\n        \"\"\"Delete all playlists in a scope.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.delete_scope,\n                ({\"scope_type\": scope_type}),\n            )\n\n    async def create_table(self):\n        \"\"\"Create the playlist table.\"\"\"\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(self.database.cursor().execute, PLAYLIST_CREATE_TABLE)\n\n    async def upsert(\n        self,\n        scope: str,\n        playlist_id: int,\n        playlist_name: str,\n        scope_id: int,\n        author_id: int,\n        playlist_url: Optional[str],\n        tracks: List[MutableMapping],\n    ):\n        \"\"\"Insert or update a playlist into the database.\"\"\"\n        scope_type = self.get_scope_type(scope)\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.upsert,\n                {\n                    \"scope_type\": str(scope_type),\n                    \"playlist_id\": int(playlist_id),\n                    \"playlist_name\": str(playlist_name),\n                    \"scope_id\": int(scope_id),\n                    \"author_id\": int(author_id),\n                    \"playlist_url\": playlist_url,\n                    \"tracks\": json.dumps(tracks),\n                },\n            )\n\n    async def handle_playlist_user_id_deletion(self, user_id: int):\n        with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:\n            executor.submit(\n                self.database.cursor().execute,\n                self.statement.drop_user_playlists,\n                {\"user_id\": user_id},\n            )\n", "path": "redbot/cogs/audio/apis/playlist_wrapper.py"}]}