Source code for flexget.components.managed_lists.lists.movie_list.api

import copy
from math import ceil

from flask import jsonify, request
from loguru import logger
from sqlalchemy.orm.exc import NoResultFound

from flexget.api import APIResource, api
from flexget.api.app import (
    BadRequest,
    Conflict,
    NotFoundError,
    base_message_schema,
    etag,
    pagination_headers,
    success_response,
)

from . import db
from .movie_list import MovieListBase

logger = logger.bind(name='movie_list')

movie_list_api = api.namespace('movie_list', description='Movie List operations')


[docs] class ObjectsContainer: input_movie_list_id_object = { 'type': 'array', 'items': {'type': 'object', 'minProperties': 1, 'additionalProperties': True}, } input_movie_entry = { 'type': 'object', 'properties': { 'movie_name': {'type': 'string'}, 'movie_year': {'type': 'integer'}, 'movie_identifiers': input_movie_list_id_object, }, 'additionalProperties': True, 'required': ['movie_name'], } return_movie_list_id_object = copy.deepcopy(input_movie_list_id_object) return_movie_list_id_object.update({ 'properties': { 'id': {'type': 'integer'}, 'added_on': {'type': 'string'}, 'movie_id': {'type': 'integer'}, } }) movie_list_object = { 'type': 'object', 'properties': { 'title': {'type': 'string'}, 'added_on': {'type': 'string'}, 'year': {'type': ['integer', 'null']}, 'list_id': {'type': 'integer'}, 'movie_list_ids': {'type': 'array', 'items': return_movie_list_id_object}, }, } list_object = { 'type': 'object', 'properties': { 'id': {'type': 'integer'}, 'added_on': {'type': 'string'}, 'name': {'type': 'string'}, }, } list_input = copy.deepcopy(list_object) del list_input['properties']['id'] del list_input['properties']['added_on'] return_movies = {'type': 'array', 'items': movie_list_object} return_lists = {'type': 'array', 'items': list_object} return_identifiers = {'type': 'array', 'items': {'type': 'string'}} batch_ids = {'type': 'array', 'items': {'type': 'integer'}, 'uniqueItems': True, 'minItems': 1} batch_remove_object = { 'type': 'object', 'properties': {'ids': batch_ids}, 'required': ['ids'], 'additionalProperties': False, }
input_movie_entry_schema = api.schema_model( 'input_movie_entry', ObjectsContainer.input_movie_entry ) input_movie_list_id_schema = api.schema_model( 'input_movie_list_id_object', ObjectsContainer.input_movie_list_id_object ) movie_list_id_object_schema = api.schema_model( 'movie_list_id_object', ObjectsContainer.return_movie_list_id_object ) movie_list_object_schema = api.schema_model( 'movie_list_object', ObjectsContainer.movie_list_object ) list_object_schema = api.schema_model('list_object', ObjectsContainer.list_object) return_lists_schema = api.schema_model('return_lists', ObjectsContainer.return_lists) return_movies_schema = api.schema_model('return_movies', ObjectsContainer.return_movies) new_list_schema = api.schema_model('new_list', ObjectsContainer.list_input) identifiers_schema = api.schema_model( 'movie_list.identifiers', ObjectsContainer.return_identifiers ) movie_list_batch_remove_schema = api.schema_model( 'movie_list.batch_remove_object', ObjectsContainer.batch_remove_object ) movie_list_parser = api.parser() movie_list_parser.add_argument('name', help='Filter results by list name')
[docs] @movie_list_api.route('/') class MovieListAPI(APIResource):
[docs] @etag @api.response(200, model=return_lists_schema) @api.doc(expect=[movie_list_parser]) def get(self, session=None): """Get movies lists.""" args = movie_list_parser.parse_args() name = args.get('name') movie_lists = [ movie_list.to_dict() for movie_list in db.get_movie_lists(name=name, session=session) ] return jsonify(movie_lists)
[docs] @api.validate(new_list_schema) @api.response(201, model=list_object_schema) @api.response(Conflict) def post(self, session=None): """Create a new list.""" data = request.json name = data.get('name') try: movie_list = db.get_list_by_exact_name(name=name, session=session) except NoResultFound: movie_list = None if movie_list: raise Conflict(f"list with name '{name}' already exists") movie_list = db.MovieListList(name=name) session.add(movie_list) session.commit() resp = jsonify(movie_list.to_dict()) resp.status_code = 201 return resp
[docs] @movie_list_api.route('/<int:list_id>/') @api.doc(params={'list_id': 'ID of the list'}) class MovieListListAPI(APIResource):
[docs] @etag @api.response(NotFoundError) @api.response(200, model=list_object_schema) def get(self, list_id, session=None): """Get list by ID.""" try: movie_list = db.get_list_by_id(list_id=list_id, session=session) except NoResultFound: raise NotFoundError(f'list_id {list_id} does not exist') return jsonify(movie_list.to_dict())
[docs] @api.response(200, model=base_message_schema) @api.response(404) def delete(self, list_id, session=None): """Delete list by ID.""" try: movie_list = db.get_list_by_id(list_id=list_id, session=session) except NoResultFound: raise NotFoundError(f'list_id {list_id} does not exist') session.delete(movie_list) return success_response('successfully deleted list')
movie_identifiers_doc = ( "Use movie identifier using the following format:\n[{'ID_NAME: 'ID_VALUE'}]." ) sort_choices = ('id', 'added', 'title', 'year') movies_parser = api.pagination_parser(sort_choices=sort_choices, default='title')
[docs] @movie_list_api.route('/<int:list_id>/movies/') class MovieListMoviesAPI(APIResource):
[docs] @etag @api.response(NotFoundError) @api.response(200, model=return_movies_schema) @api.doc(params={'list_id': 'ID of the list'}, expect=[movies_parser]) def get(self, list_id, session=None): """Get movies by list ID.""" args = movies_parser.parse_args() # Pagination and sorting params page = args['page'] per_page = args['per_page'] sort_by = args['sort_by'] sort_order = args['order'] start = per_page * (page - 1) stop = start + per_page descending = sort_order == 'desc' kwargs = { 'start': start, 'stop': stop, 'list_id': list_id, 'order_by': sort_by, 'descending': descending, 'session': session, } try: list = db.get_list_by_id(list_id=list_id, session=session) except NoResultFound: raise NotFoundError(f'list_id {list_id} does not exist') total_items = list.movies.count() if not total_items: return jsonify([]) movies = [movie.to_dict() for movie in db.get_movies_by_list_id(**kwargs)] total_pages = ceil(total_items / float(per_page)) if page > total_pages: raise NotFoundError(f'page {page} does not exist') # Actual results in page actual_size = min(len(movies), per_page) # Get pagination headers pagination = pagination_headers(total_pages, total_items, actual_size, request) # Create response rsp = jsonify(movies) # Add link header to response rsp.headers.extend(pagination) return rsp
[docs] @api.validate(model=input_movie_entry_schema, description=movie_identifiers_doc) @api.response(201, model=movie_list_object_schema) @api.response(NotFoundError) @api.response(Conflict) @api.response(BadRequest) def post(self, list_id, session=None): """Add movies to list by ID.""" try: db.get_list_by_id(list_id=list_id, session=session) except NoResultFound: raise NotFoundError(f'list_id {list_id} does not exist') data = request.json movie_identifiers = data.get('movie_identifiers', []) # Validates ID type based on allowed ID for id_name in movie_identifiers: if next(iter(id_name)) not in MovieListBase().supported_ids: raise BadRequest(f'movie identifier {id_name} is not allowed') title, year = data['movie_name'], data.get('movie_year') movie = db.get_movie_by_title_and_year( list_id=list_id, title=title, year=year, session=session ) if movie: raise Conflict(f'movie with name "{title}" already exist in list {list_id}') movie = db.MovieListMovie() movie.title = title movie.year = year movie.ids = db.get_db_movie_identifiers(identifier_list=movie_identifiers, session=session) movie.list_id = list_id session.add(movie) session.commit() response = jsonify(movie.to_dict()) response.status_code = 201 return response
[docs] @movie_list_api.route('/<int:list_id>/movies/<int:movie_id>/') @api.doc(params={'list_id': 'ID of the list', 'movie_id': 'ID of the movie'}) @api.response(NotFoundError) class MovieListMovieAPI(APIResource):
[docs] @etag @api.response(200, model=movie_list_object_schema) def get(self, list_id, movie_id, session=None): """Get a movie by list ID and movie ID.""" try: movie = db.get_movie_by_id(list_id=list_id, movie_id=movie_id, session=session) except NoResultFound: raise NotFoundError(f'could not find movie with id {movie_id} in list {list_id}') return jsonify(movie.to_dict())
[docs] @api.response(200, model=base_message_schema) def delete(self, list_id, movie_id, session=None): """Delete a movie by list ID and movie ID.""" try: movie = db.get_movie_by_id(list_id=list_id, movie_id=movie_id, session=session) except NoResultFound: raise NotFoundError(f'could not find movie with id {movie_id} in list {list_id}') logger.debug('deleting movie {}', movie.id) session.delete(movie) return success_response(f'successfully deleted movie {movie_id}')
[docs] @api.validate(model=input_movie_list_id_schema, description=movie_identifiers_doc) @api.response(200, model=movie_list_object_schema) @api.response(BadRequest) @api.doc( description='Sent movie identifiers will override any existing identifiers that the movie currently holds' ) def put(self, list_id, movie_id, session=None): """Set movie identifiers.""" try: movie = db.get_movie_by_id(list_id=list_id, movie_id=movie_id, session=session) except NoResultFound: raise NotFoundError(f'could not find movie with id {movie_id} in list {list_id}') data = request.json # Validates ID type based on allowed ID for id_name in data: if next(iter(id_name)) not in MovieListBase().supported_ids: raise BadRequest(f'movie identifier {id_name} is not allowed') movie.ids[:] = db.get_db_movie_identifiers( identifier_list=data, movie_id=movie_id, session=session ) session.commit() return jsonify(movie.to_dict())
[docs] @movie_list_api.route('/identifiers/') @api.doc(description='A list of valid movie identifiers to be used when creating or editing movie') class MovieListIdentifiers(APIResource):
[docs] @api.response(200, model=identifiers_schema) def get(self, session=None): """Return a list of supported movie list identifiers.""" return jsonify(MovieListBase().supported_ids)
[docs] @movie_list_api.route('/<int:list_id>/movies/batch') @api.doc(params={'list_id': 'ID of the list'}) @api.response(NotFoundError) class MovieListEntriesBatchAPI(APIResource):
[docs] @api.response(204) @api.validate(model=movie_list_batch_remove_schema) @api.doc(description='Remove multiple entries') def delete(self, list_id, session=None): """Remove multiple entries.""" data = request.json movie_ids = data.get('ids') try: movies = db.get_movies_by_list_id(list_id, movie_ids=movie_ids, session=session) except NoResultFound: raise NotFoundError(f'could not find movies in list {list_id}') for movie in movies: session.delete(movie) session.commit() response = jsonify([]) response.status_code = 204 return response