1616 Any ,
1717 Awaitable ,
1818 Callable ,
19+ Literal ,
1920 ParamSpec ,
2021 Sequence ,
2122 TypedDict ,
@@ -158,20 +159,7 @@ async def wrapper(*args: P.args, **kwargs: P.kwargs) -> Response:
158159 return make_response
159160
160161
161- @resource_bp .route ("/item/<int:id>" , methods = ["GET" , "DELETE" , "PATCH" ])
162- @resource (Item , patchable = True )
163- async def item (id : int ):
164- item = g .lib .get_item (id )
165- if not item :
166- raise NotFoundException (f"Item with beets_id:'{ id } ' not found in beets db." )
167-
168- return item
169-
170-
171- @resource_bp .route ("/item/query/<path:query>" , methods = ["GET" , "DELETE" , "PATCH" ])
172- @resource_query (Item , patchable = True )
173- async def item_query (query : str ):
174- return g .lib .items (query )
162+ # ---------------------------------- Albums ---------------------------------- #
175163
176164
177165@resource_bp .route ("/album/<int:id>" , methods = ["GET" , "DELETE" , "PATCH" ])
@@ -183,57 +171,6 @@ async def album(id: int):
183171 return item
184172
185173
186- @resource_bp .route ("/album/query/<path:query>" , methods = ["GET" , "DELETE" , "PATCH" ])
187- @resource_query (Album , patchable = False )
188- async def album_query (query : str ):
189- return g .lib .albums (query )
190-
191-
192- # Artists are handled slightly differently, as they are not a beets model but can be
193- # derived from the items.
194- @resource_bp .route ("/artist/<path:artist_name>/albums" , methods = ["GET" ])
195- async def albums_by_artist (artist_name : str ):
196- """Get all items for a specific artist."""
197- log .debug (f"Album query for artist '{ artist_name } '" )
198-
199- with g .lib .transaction () as tx :
200- rows = tx .query (
201- f"SELECT id FROM albums WHERE instr(albumartist, ?) > 0" ,
202- (artist_name ,),
203- )
204-
205- expanded = expanded_response ()
206- minimal = minimal_response ()
207-
208- return jsonify (
209- [
210- _rep (g .lib .get_album (row [0 ]), expand = expanded , minimal = minimal )
211- for row in rows
212- ]
213- )
214-
215-
216- # Items by artist are handled slightly differently, as they are not a beets model but can be
217- # derived from the items.
218- @resource_bp .route ("/artist/<path:artist_name>/items" , methods = ["GET" ])
219- async def items_by_artist (artist_name : str ):
220- """Get all items for a specific artist."""
221- log .debug (f"Item query for artist '{ artist_name } '" )
222-
223- with g .lib .transaction () as tx :
224- rows = tx .query (
225- f"SELECT id FROM items WHERE instr(artist, ?) > 0" ,
226- (artist_name ,),
227- )
228-
229- expanded = expanded_response ()
230- minimal = minimal_response ()
231-
232- return jsonify (
233- [_rep (g .lib .get_item (row [0 ]), expand = expanded , minimal = minimal ) for row in rows ]
234- )
235-
236-
237174@resource_bp .route ("/album/bf_id/<string:bf_id>" , methods = ["GET" ])
238175@resource (Album , patchable = False )
239176async def album_by_bf_id (bf_id : str ):
@@ -292,7 +229,6 @@ async def all_albums(query: str = ""):
292229
293230 sub_query = parse_query_string (query , Album )
294231
295- start = time .perf_counter ()
296232 paginated_query = PaginatedQuery (
297233 cursor = cursor ,
298234 sub_query = sub_query ,
@@ -322,6 +258,128 @@ async def all_albums(query: str = ""):
322258 )
323259
324260
261+ # Artists are handled slightly differently, as they are not a beets model but can be
262+ # derived from the items.
263+ @resource_bp .route ("/artist/<path:artist_name>/albums" , methods = ["GET" ])
264+ async def albums_by_artist (artist_name : str ):
265+ """Get all items for a specific artist."""
266+ log .debug (f"Album query for artist '{ artist_name } '" )
267+
268+ with g .lib .transaction () as tx :
269+ rows = tx .query (
270+ f"SELECT id FROM albums WHERE instr(albumartist, ?) > 0" ,
271+ (artist_name ,),
272+ )
273+
274+ expanded = expanded_response ()
275+ minimal = minimal_response ()
276+
277+ return jsonify (
278+ [
279+ _rep (g .lib .get_album (row [0 ]), expand = expanded , minimal = minimal )
280+ for row in rows
281+ ]
282+ )
283+
284+
285+ # ----------------------------------- Items ---------------------------------- #
286+
287+
288+ @resource_bp .route ("/item/<int:id>" , methods = ["GET" , "DELETE" , "PATCH" ])
289+ @resource (Item , patchable = True )
290+ async def item (id : int ):
291+ item = g .lib .get_item (id )
292+ if not item :
293+ raise NotFoundException (f"Item with beets_id:'{ id } ' not found in beets db." )
294+
295+ return item
296+
297+
298+ @resource_bp .route ("/items" , methods = ["GET" ], defaults = {"query" : "" })
299+ @resource_bp .route ("/items/<path:query>" , methods = ["GET" ])
300+ async def all_items (query : str = "" ):
301+ """Get all items in the library.
302+
303+ If a query is provided, it will be used to filter the items.
304+ """
305+ log .debug (f"Item query: { query } " )
306+ params = dict (request .args )
307+ cursor = pop_query_param (params , "cursor" , Cursor .from_string , None )
308+ if cursor is None :
309+ order_by_column = pop_query_param (params , "order_by" , str , "added" )
310+ order_by_direction = pop_query_param (params , "order_dir" , str , "DESC" )
311+ cursor = Cursor (
312+ order_by_column = order_by_column ,
313+ order_by_direction = order_by_direction ,
314+ last_order_by_value = None ,
315+ last_id = None ,
316+ )
317+
318+ n_items = pop_query_param (
319+ params ,
320+ "n_items" ,
321+ int ,
322+ 50 , # Default number of items per page
323+ )
324+
325+ if len (params ) > 0 :
326+ raise InvalidUsageException (
327+ "Unexpected query parameters: , " .join (params .keys ())
328+ )
329+
330+ sub_query = parse_query_string (query , Item )
331+
332+ paginated_query = PaginatedQuery (
333+ cursor = cursor ,
334+ sub_query = sub_query ,
335+ n_items = n_items ,
336+ table = "items" ,
337+ )
338+ items = list (g .lib .items (paginated_query , paginated_query ))
339+
340+ # Update cursor
341+ next_url : str | None = None
342+
343+ total = paginated_query .total (g .lib )
344+ if len (items ) == n_items and len (items ) > 0 :
345+ last_item = items [- 1 ]
346+
347+ cursor .last_order_by_value = str (
348+ getattr (last_item , cursor .order_by_column , None )
349+ )
350+ cursor .last_id = str (last_item .id )
351+ next_url = f"{ request .path } ?cursor={ cursor .to_string ()} &n_items={ n_items } "
352+
353+ return jsonify (
354+ {
355+ "items" : [_rep (item , expand = False , minimal = True ) for item in items ],
356+ "next" : next_url ,
357+ "total" : total ,
358+ }
359+ )
360+
361+
362+ # Items by artist are handled slightly differently, as they are not a beets model but can be
363+ # derived from the items.
364+ @resource_bp .route ("/artist/<path:artist_name>/items" , methods = ["GET" ])
365+ async def items_by_artist (artist_name : str ):
366+ """Get all items for a specific artist."""
367+ log .debug (f"Item query for artist '{ artist_name } '" )
368+
369+ with g .lib .transaction () as tx :
370+ rows = tx .query (
371+ f"SELECT id FROM items WHERE instr(artist, ?) > 0" ,
372+ (artist_name ,),
373+ )
374+
375+ expanded = expanded_response ()
376+ minimal = minimal_response ()
377+
378+ return jsonify (
379+ [_rep (g .lib .get_item (row [0 ]), expand = expanded , minimal = minimal ) for row in rows ]
380+ )
381+
382+
325383# ----------------------------------- Util ----------------------------------- #
326384
327385
@@ -429,13 +487,20 @@ class PaginatedQuery(Query, Sort):
429487
430488 _sub_query : tuple [Query , Sort ] | None
431489
490+ table : Literal ["albums" , "items" ]
491+
432492 def __init__ (
433- self , cursor : Cursor , sub_query : tuple [Query , Sort ], n_items = 50
493+ self ,
494+ cursor : Cursor ,
495+ sub_query : tuple [Query , Sort ],
496+ n_items = 50 ,
497+ table : Literal ["albums" , "items" ] = "albums" ,
434498 ) -> None :
435499 super ().__init__ ()
436500 self .n_items = n_items
437501 self .cursor = cursor
438502 self ._sub_query = sub_query
503+ self .table = table
439504
440505 def clause (self ) -> tuple [str | None , Sequence [Any ]]:
441506 """Return the SQL clause and values for the query."""
@@ -472,7 +537,7 @@ def total(self, lib: Library) -> int:
472537 vs = ()
473538
474539 with g .lib .transaction () as tx :
475- count = tx .query (f"SELECT COUNT(*) FROM albums WHERE { cs } " , vs )[0 ][0 ]
540+ count = tx .query (f"SELECT COUNT(*) FROM { self . table } WHERE { cs } " , vs )[0 ][0 ]
476541 return count
477542
478543
0 commit comments