def cdb_run(ctx, rd, which, version): try: m = module_for_cmd(which) except ImportError: raise HTTPNotFound('No module named: {}'.format(which)) if not getattr(m, 'readonly', False): ctx.check_for_write_access(rd) if getattr(m, 'version', 0) != int(version): raise HTTPNotFound( ('The module {} is not available in version: {}.' 'Make sure the version of calibre used for the' ' server and calibredb match').format(which, version)) db = get_library_data(ctx, rd, strict_library_id=True)[0] if ctx.restriction_for(rd, db): raise HTTPForbidden( 'Cannot use the command-line db interface with a user who has per library restrictions' ) raw = rd.read() ct = rd.inheaders.get('Content-Type', all=True) ct = {x.lower().partition(';')[0] for x in ct} try: if MSGPACK_MIME in ct: args = msgpack_loads(raw) elif 'application/json' in ct: args = json_loads(raw) else: raise HTTPBadRequest('Only JSON or msgpack requests are supported') except Exception: raise HTTPBadRequest('args are not valid encoded data') if getattr(m, 'needs_srv_ctx', False): args = [ctx] + list(args) try: result = m.implementation( db, partial(ctx.notify_changes, db.backend.library_path), *args) except Exception as err: import traceback return {'err': as_unicode(err), 'tb': traceback.format_exc()} return {'result': result}
def conversion_status(ctx, rd, job_id): with cache_lock: job_status = conversion_jobs.get(job_id) if job_status is None: raise HTTPNotFound(f'No job with id: {job_id}') job_status.last_check_at = monotonic() if job_status.running: percent, msg = job_status.current_status if rd.query.get('abort_job'): ctx.abort_job(job_id) return {'running': True, 'percent': percent, 'msg': msg} del conversion_jobs[job_id] try: ans = { 'running': False, 'ok': job_status.ok, 'was_aborted': job_status.was_aborted, 'traceback': job_status.traceback, 'log': job_status.log } if job_status.ok: db, library_id = get_library_data(ctx, rd)[:2] if library_id != job_status.library_id: raise HTTPNotFound('job library_id does not match') fmt = job_status.output_path.rpartition('.')[-1] try: db.add_format(job_status.book_id, fmt, job_status.output_path) except NoSuchBook: raise HTTPNotFound( f'book_id {job_status.book_id} not found in library') formats_added({job_status.book_id: (fmt, )}) ans['size'] = os.path.getsize(job_status.output_path) ans['fmt'] = fmt return ans finally: job_status.cleanup()
def set_last_read_position(ctx, rd, library_id, book_id, fmt): db = get_db(ctx, rd, library_id) user = rd.username or None if not ctx.has_id(rd, db, book_id): raise BookNotFound(book_id, db) try: data = jsonlib.load(rd.request_body_file) device, cfi, pos_frac = data['device'], data['cfi'], data['pos_frac'] except Exception: raise HTTPNotFound('Invalid data') db.set_last_read_position( book_id, fmt, user=user, device=device, cfi=cfi or None, pos_frac=pos_frac) rd.outheaders['Content-type'] = 'text/plain' return b''
def find_route(self, path): size = len(path) # routes for which min_size <= size <= max_size routes = self.max_size_map.get(size, set()) & self.min_size_map.get(size, set()) for route in sorted(routes, key=attrgetter('max_size'), reverse=True): args = route.matches(path) if args is not False: return route.endpoint, args for route in self.soak_routes: if route.min_size <= size: args = route.matches(path) if args is not False: return route.endpoint, args raise HTTPNotFound()
def opds_category(ctx, rd, category, which): try: offset = int(rd.query.get('offset', 0)) except Exception: raise HTTPNotFound('Not found') if not which or not category: raise HTTPNotFound('Not found') rc = RequestContext(ctx, rd) page_url = rc.url_for('/opds/category', which=which, category=category) up_url = rc.url_for('/opds/navcatalog', which=category) which, category = from_hex_unicode(which), from_hex_unicode(category) type_ = which[0] which = which[1:] if type_ == 'I': try: p = which.rindex(':') category = which[p + 1:] which = which[:p] # This line will toss an exception for composite columns which = int(which[:p]) except Exception: # Might be a composite column, where we have the lookup key if not (category in rc.db.field_metadata and rc.db.field_metadata[category]['datatype'] == 'composite'): raise HTTPNotFound('Tag %r not found' % which) categories = rc.get_categories() if category not in categories: raise HTTPNotFound('Category %r not found' % which) if category == 'search': try: ids = rc.search('search:"%s"' % which) except Exception: raise HTTPNotFound('Search: %r not understood' % which) return get_acquisition_feed(rc, ids, offset, page_url, up_url, 'calibre-search:' + which) if type_ != 'I': raise HTTPNotFound('Non id categories not supported') q = category if q == 'news': q = 'tags' ids = rc.db.get_books_for_category(q, which) & rc.allowed_book_ids() sort_by = 'series' if category == 'series' else 'title' return get_acquisition_feed(rc, ids, offset, page_url, up_url, 'calibre-category:' + category + ':' + unicode_type(which), sort_by=sort_by)
def book_manifest(ctx, rd, book_id, fmt): db, library_id = get_library_data(ctx, rd)[:2] force_reload = rd.query.get('force_reload') == '1' if plugin_for_input_format(fmt) is None: raise HTTPNotFound('The format %s cannot be viewed' % fmt.upper()) if not ctx.has_id(rd, db, book_id): raise BookNotFound(book_id, db) with db.safe_read_lock: fm = db.format_metadata(book_id, fmt, allow_cache=False) if not fm: raise HTTPNotFound(f'No {fmt} format for the book (id:{book_id}) in the library: {library_id}') size, mtime = map(int, (fm['size'], time.mktime(fm['mtime'].utctimetuple())*10)) bhash = book_hash(db.library_id, book_id, fmt, size, mtime) with cache_lock: mpath = abspath(os.path.join(books_cache_dir(), 'f', bhash, 'calibre-book-manifest.json')) if force_reload: safe_remove(mpath, True) try: os.utime(mpath, None) with lopen(mpath, 'rb') as f: ans = jsonlib.load(f) ans['metadata'] = book_as_json(db, book_id) user = rd.username or None ans['last_read_positions'] = db.get_last_read_positions(book_id, fmt, user) if user else [] ans['annotations_map'] = db.annotations_map_for_book(book_id, fmt, user_type='web', user=user or '*') return ans except OSError as e: if e.errno != errno.ENOENT: raise x = failed_jobs.pop(bhash, None) if x is not None: return {'aborted':x[0], 'traceback':x[1], 'job_status':'finished'} job_id = queued_jobs.get(bhash) if job_id is None: job_id = queue_job(ctx, partial(db.copy_format_to, book_id, fmt), bhash, fmt, book_id, size, mtime) status, result, tb, aborted = ctx.job_status(job_id) return {'aborted': aborted, 'traceback':tb, 'job_status':status, 'job_id':job_id}
def update_annotations(ctx, rd, library_id, book_id, fmt): db = get_db(ctx, rd, library_id) user = rd.username or '*' if not ctx.has_id(rd, db, book_id): raise BookNotFound(book_id, db) try: amap = jsonlib.load(rd.request_body_file) except Exception: raise HTTPNotFound('Invalid data') alist = [] for val in itervalues(amap): if val: alist.extend(val) db.merge_annotations_for_book(book_id, fmt, alist, user_type='web', user=user) return b''
def books(ctx, rd): ''' Get data to create list of books Optional: ?num=50&sort=timestamp.desc&library_id=<default library> &search=''&extra_books=''&vl='' ''' ans = {} try: num = int(rd.query.get('num', rd.opts.num_per_page)) except Exception: raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num')) library_id, db, sorts, orders, vl = get_basic_query_data(ctx, rd) ans = get_library_init_data(ctx, rd, db, num, sorts, orders, vl) ans['library_id'] = library_id return ans
def books(ctx, rd, library_id): ''' Return the metadata for the books as a JSON dictionary. Query parameters: ?ids=all&category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: id_is_uuid = rd.query.get('id_is_uuid', 'false') ids = rd.query.get('ids') if ids is None or ids == 'all': ids = db.all_book_ids() else: ids = ids.split(',') if id_is_uuid == 'true': ids = {db.lookup_by_uuid(x) for x in ids} ids.discard(None) else: try: ids = {int(x) for x in ids} except Exception: raise HTTPNotFound('ids must a comma separated list of integers') last_modified = None category_urls = rd.query.get('category_urls', 'true').lower() == 'true' device_compatible = rd.query.get('device_compatible', 'false').lower() == 'true' device_for_template = rd.query.get('device_for_template', None) ans = {} restricted_to = ctx.allowed_book_ids(rd, db) for book_id in ids: if book_id not in restricted_to: ans[book_id] = None continue data, lm = book_to_json( ctx, rd, db, book_id, get_category_urls=category_urls, device_compatible=device_compatible, device_for_template=device_for_template) last_modified = lm if last_modified is None else max(lm, last_modified) ans[book_id] = data if last_modified is not None: rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return ans
def __init__(self, offset, delta, total): if offset < 0: offset = 0 if offset >= total: raise HTTPNotFound('Invalid offset: %r' % offset) last_allowed_index = total - 1 last_current_index = offset + delta - 1 self.slice_upper_bound = offset + delta self.offset = offset self.next_offset = last_current_index + 1 if self.next_offset > last_allowed_index: self.next_offset = -1 self.previous_offset = self.offset - delta if self.previous_offset < 0: self.previous_offset = 0 self.last_offset = last_allowed_index - delta if self.last_offset < 0: self.last_offset = 0
def get_navcatalog(request_context, which, page_url, up_url, offset=0): categories = request_context.get_categories() if which not in categories: raise HTTPNotFound('Category %r not found'%which) items = categories[which] updated = request_context.last_modified() category_meta = request_context.db.field_metadata meta = category_meta.get(which, {}) category_name = meta.get('name', which) feed_title = default_feed_title + ' :: ' + _('By %s') % category_name id_ = 'calibre-category-feed:'+which MAX_ITEMS = request_context.opts.max_opds_ungrouped_items if MAX_ITEMS > 0 and len(items) <= MAX_ITEMS: max_items = request_context.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = list(items)[offsets.offset:offsets.offset+max_items] ans = CategoryFeed(items, which, id_, updated, request_context, offsets, page_url, up_url, title=feed_title) else: Group = namedtuple('Group', 'text count') starts = set() for x in items: val = getattr(x, 'sort', x.name) if not val: val = 'A' starts.add(val[0].upper()) category_groups = OrderedDict() for x in sorted(starts, key=sort_key): category_groups[x] = len([y for y in items if getattr(y, 'sort', y.name).upper().startswith(x)]) items = [Group(x, y) for x, y in category_groups.items()] max_items = request_context.opts.max_opds_items offsets = Offsets(offset, max_items, len(items)) items = items[offsets.offset:offsets.offset+max_items] ans = CategoryGroupFeed(items, which, id_, updated, request_context, offsets, page_url, up_url, title=feed_title) request_context.outheaders['Last-Modified'] = http_date(timestampfromdt(updated)) return ans.root
def book(ctx, rd, book_id, library_id): ''' Return the metadata of the book as a JSON dictionary. Query parameters: ?category_urls=true&id_is_uuid=false&device_for_template=None If category_urls is true the returned dictionary also contains a mapping of category (field) names to URLs that return the list of books in the given category. If id_is_uuid is true then the book_id is assumed to be a book uuid instead. ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: id_is_uuid = rd.query.get('id_is_uuid', 'false') oid = book_id if id_is_uuid == 'true': book_id = db.lookup_by_uuid(book_id) else: try: book_id = int(book_id) if not db.has_id(book_id): book_id = None except Exception: book_id = None if book_id is None or book_id not in ctx.allowed_book_ids(rd, db): raise HTTPNotFound('Book with id %r does not exist' % oid) category_urls = rd.query.get('category_urls', 'true').lower() device_compatible = rd.query.get('device_compatible', 'false').lower() device_for_template = rd.query.get('device_for_template', None) data, last_modified = book_to_json( ctx, rd, db, book_id, get_category_urls=category_urls == 'true', device_compatible=device_compatible == 'true', device_for_template=device_for_template) rd.outheaders['Last-Modified'] = http_date(timestampfromdt(last_modified)) return data
def search_result(ctx, rd, db, query, num, offset, sort, sort_order, vl=''): multisort = [(sanitize_sort_field_name(db.field_metadata, s), ensure_val(o, 'asc', 'desc') == 'asc') for s, o in zip(sort.split(','), cycle(sort_order.split(',')))] skeys = db.field_metadata.sortable_field_keys() for sfield, sorder in multisort: if sfield not in skeys: raise HTTPNotFound('%s is not a valid sort field'%sort) ids = ctx.search(rd, db, query, vl=vl) ids = db.multisort(fields=multisort, ids_to_sort=ids) total_num = len(ids) ids = ids[offset:offset+num] return { 'total_num': total_num, 'sort_order':sort_order, 'offset':offset, 'num':len(ids), 'sort':sort, 'base_url':ctx.url_for(search, library_id=db.server_library_id), 'query': query, 'library_id': db.server_library_id, 'book_ids':ids, 'vl': vl, }
def get_last_read_position(ctx, rd, library_id, which): ''' Get last read position data for the specified books, where which is of the form: book_id1-fmt1_book_id2-fmt2,... ''' db = get_db(ctx, rd, library_id) user = rd.username or None if not user: raise HTTPNotFound('login required for sync') ans = {} allowed_book_ids = ctx.allowed_book_ids(rd, db) for item in which.split('_'): book_id, fmt = item.partition('-')[::2] try: book_id = int(book_id) except Exception: continue if book_id not in allowed_book_ids: continue key = '{}:{}'.format(book_id, fmt) ans[key] = db.get_last_read_positions(book_id, fmt, user) return ans
def search_result(ctx, rd, db, query, num, offset, sort, sort_order, vl=''): multisort = [ (sanitize_sort_field_name(db.field_metadata, s), ensure_val(o, 'asc', 'desc') == 'asc') for s, o in zip(sort.split(','), cycle(sort_order.split(','))) ] skeys = db.field_metadata.sortable_field_keys() for sfield, sorder in multisort: if sfield not in skeys: raise HTTPNotFound('%s is not a valid sort field' % sort) ids, parse_error = ctx.search(rd, db, query, vl=vl, report_restriction_errors=True) ids = db.multisort(fields=multisort, ids_to_sort=ids) total_num = len(ids) ids = ids[offset:offset + num] num_books = db.number_of_books_in_virtual_library( vl) if query else total_num ans = { 'total_num': total_num, 'sort_order': sort_order, 'num_books_without_search': num_books, 'offset': offset, 'num': len(ids), 'sort': sort, 'base_url': ctx.url_for(search, library_id=db.server_library_id), 'query': query, 'library_id': db.server_library_id, 'book_ids': ids, 'vl': vl, } if parse_error is not None: ans['bad_restriction'] = str(parse_error) return ans
def category(ctx, rd, encoded_name, library_id): ''' Return a dictionary describing the category specified by name. The Optional: ?num=100&offset=0&sort=name&sort_order=asc The dictionary looks like:: { 'category_name': Category display name, 'base_url': Base URL for this category, 'total_num': Total numberof items in this category, 'offset': The offset for the items returned in this result, 'num': The number of items returned in this result, 'sort': How the returned items are sorted, 'sort_order': asc or desc 'subcategories': List of sub categories of this category. 'items': List of items in this category, } Each subcategory is a dictionary of the same form as those returned by /ajax/categories Each item is a dictionary of the form:: { 'name': Display name, 'average_rating': Average rating for books in this item, 'count': Number of books in this item, 'url': URL to get list of books in this item, 'has_children': If True this item contains sub categories, look for an entry corresponding to this item in subcategories int he main dictionary, } :param sort: How to sort the returned items. Choices are: name, rating, popularity :param sort_order: asc or desc To learn how to create subcategories see https://manual.calibre-ebook.com/sub_groups.html ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: num, offset = get_pagination(rd.query) sort, sort_order = rd.query.get('sort'), rd.query.get('sort_order') sort = ensure_val(sort, 'name', 'rating', 'popularity') sort_order = ensure_val(sort_order, 'asc', 'desc') try: dname = decode_name(encoded_name) except: raise HTTPNotFound('Invalid encoding of category name %r'%encoded_name) base_url = ctx.url_for(globals()['category'], encoded_name=encoded_name, library_id=db.server_library_id) if dname in ('newest', 'allbooks'): sort, sort_order = 'timestamp', 'desc' rd.query['sort'], rd.query['sort_order'] = sort, sort_order return books_in(ctx, rd, encoded_name, encode_name('0'), library_id) fm = db.field_metadata categories = ctx.get_categories(rd, db) hierarchical_categories = db.pref('categories_using_hierarchy', ()) subcategory = dname toplevel = subcategory.partition('.')[0] if toplevel == subcategory: subcategory = None if toplevel not in categories or toplevel not in fm: raise HTTPNotFound('Category %r not found'%toplevel) # Find items and sub categories subcategories = [] meta = fm[toplevel] item_names = {} children = set() if meta['kind'] == 'user': fullname = ((toplevel + '.' + subcategory) if subcategory is not None else toplevel) try: # User categories cannot be applied to books, so this is the # complete set of items, no need to consider sub categories items = categories[fullname] except: raise HTTPNotFound('User category %r not found'%fullname) parts = fullname.split('.') for candidate in categories: cparts = candidate.split('.') if len(cparts) == len(parts)+1 and cparts[:-1] == parts: subcategories.append({'name':cparts[-1], 'url':candidate, 'icon':category_icon(toplevel, meta)}) category_name = toplevel[1:].split('.') # When browsing by user categories we ignore hierarchical normal # columns, so children can be empty elif toplevel in hierarchical_categories: items = [] category_names = [x.original_name.split('.') for x in categories[toplevel] if '.' in x.original_name] if subcategory is None: children = set(x[0] for x in category_names) category_name = [meta['name']] items = [x for x in categories[toplevel] if '.' not in x.original_name] else: subcategory_parts = subcategory.split('.')[1:] category_name = [meta['name']] + subcategory_parts lsp = len(subcategory_parts) children = set('.'.join(x) for x in category_names if len(x) == lsp+1 and x[:lsp] == subcategory_parts) items = [x for x in categories[toplevel] if x.original_name in children] item_names = {x:x.original_name.rpartition('.')[-1] for x in items} # Only mark the subcategories that have children themselves as # subcategories children = set('.'.join(x[:lsp+1]) for x in category_names if len(x) > lsp+1 and x[:lsp] == subcategory_parts) subcategories = [{'name':x.rpartition('.')[-1], 'url':toplevel+'.'+x, 'icon':category_icon(toplevel, meta)} for x in children] else: items = categories[toplevel] category_name = meta['name'] for x in subcategories: x['url'] = ctx.url_for(globals()['category'], encoded_name=encode_name(x['url']), library_id=db.server_library_id) x['icon'] = ctx.url_for(get_icon, which=x['icon']) x['is_category'] = True sort_keygen = { 'name': lambda x: sort_key(x.sort if x.sort else x.original_name), 'popularity': lambda x: x.count, 'rating': lambda x: x.avg_rating } items.sort(key=sort_keygen[sort], reverse=sort_order == 'desc') total_num = len(items) items = items[offset:offset+num] items = [{ 'name':item_names.get(x, x.original_name), 'average_rating': x.avg_rating, 'count': x.count, 'url': ctx.url_for(books_in, encoded_category=encode_name(x.category if x.category else toplevel), encoded_item=encode_name(x.original_name if x.id is None else unicode(x.id)), library_id=db.server_library_id ), 'has_children': x.original_name in children, } for x in items] return { 'category_name': category_name, 'base_url': base_url, 'total_num': total_num, 'offset':offset, 'num':len(items), 'sort':sort, 'sort_order':sort_order, 'subcategories':subcategories, 'items':items, }
def interface_data(ctx, rd): ''' Return the data needed to create the server main UI Optional: ?num=50&sort=timestamp.desc&library_id=<default library> &search=''&extra_books='' ''' ans = { 'username': rd.username, 'output_format': prefs['output_format'].upper(), 'input_formats': {x.upper(): True for x in available_input_formats()}, 'gui_pubdate_display_format': tweaks['gui_pubdate_display_format'], 'gui_timestamp_display_format': tweaks['gui_timestamp_display_format'], 'gui_last_modified_display_format': tweaks['gui_last_modified_display_format'], 'use_roman_numerals_for_series_number': get_use_roman(), 'translations': get_translations(), } ans['library_map'], ans['default_library'] = ctx.library_info(rd) ud = {} if rd.username: # Override session data with stored values for the authenticated user, # if any ud = ctx.user_manager.get_session_data(rd.username) lid = ud.get('library_id') if lid and lid in ans['library_map']: rd.query.set('library_id', lid) usort = ud.get('sort') if usort: rd.query.set('sort', usort) ans['library_id'], db, sorts, orders = get_basic_query_data(ctx, rd) ans['user_session_data'] = ud try: num = int(rd.query.get('num', DEFAULT_NUMBER_OF_BOOKS)) except Exception: raise HTTPNotFound('Invalid number of books: %r' % rd.query.get('num')) with db.safe_read_lock: try: ans['search_result'] = search_result(ctx, rd, db, rd.query.get('search', ''), num, 0, ','.join(sorts), ','.join(orders)) except ParseException: ans['search_result'] = search_result(ctx, rd, db, '', num, 0, ','.join(sorts), ','.join(orders)) sf = db.field_metadata.ui_sortable_field_keys() sf.pop('ondevice', None) ans['sortable_fields'] = sorted( ((sanitize_sort_field_name(db.field_metadata, k), v) for k, v in sf.iteritems()), key=lambda (field, name): sort_key(name)) ans['field_metadata'] = db.field_metadata.all_metadata() ans['icon_map'] = icon_map() ans['icon_path'] = ctx.url_for('/icon', which='') mdata = ans['metadata'] = {} try: extra_books = set( int(x) for x in rd.query.get('extra_books', '').split(',')) except Exception: extra_books = () for coll in (ans['search_result']['book_ids'], extra_books): for book_id in coll: if book_id not in mdata: data = book_as_json(db, book_id) if data is not None: mdata[book_id] = data return ans
def notfound(): raise HTTPNotFound(_('No book with id: %d in library') % book_id)
def handler(data): raise HTTPNotFound(body)
def books_in(ctx, rd, encoded_category, encoded_item, library_id): ''' Return the books (as list of ids) present in the specified category. Optional: ?num=100&offset=0&sort=title&sort_order=asc&get_additional_fields= ''' db = get_db(ctx, rd, library_id) with db.safe_read_lock: try: dname, ditem = map(decode_name, (encoded_category, encoded_item)) except: raise HTTPNotFound('Invalid encoded param: %r (%r)' % (encoded_category, encoded_item)) num, offset = get_pagination(rd.query) sort, sort_order = rd.query.get('sort', 'title'), rd.query.get('sort_order') sort_order = ensure_val(sort_order, 'asc', 'desc') sfield = sanitize_sort_field_name(db.field_metadata, sort) if sfield not in db.field_metadata.sortable_field_keys(): raise HTTPNotFound('%s is not a valid sort field' % sort) if dname in ('allbooks', 'newest'): ids = ctx.allowed_book_ids(rd, db) elif dname == 'search': try: ids = ctx.search(rd, db, 'search:"%s"' % ditem) except Exception: raise HTTPNotFound('Search: %r not understood' % ditem) else: try: cid = int(ditem) except Exception: raise HTTPNotFound('Category id %r not an integer' % ditem) if dname == 'news': dname = 'tags' ids = db.get_books_for_category(dname, cid) & ctx.allowed_book_ids( rd, db) ids = db.multisort(fields=[(sfield, sort_order == 'asc')], ids_to_sort=ids) total_num = len(ids) ids = ids[offset:offset + num] result = { 'total_num': total_num, 'sort_order': sort_order, 'offset': offset, 'num': len(ids), 'sort': sort, 'base_url': ctx.url_for(books_in, encoded_category=encoded_category, encoded_item=encoded_item, library_id=db.server_library_id), 'book_ids': ids } get_additional_fields = rd.query.get('get_additional_fields') if get_additional_fields: additional_fields = {} for field in get_additional_fields.split(','): field = field.strip() if field: flist = additional_fields[field] = [] for id_ in ids: flist.append( db.field_for(field, id_, default_value=None)) if additional_fields: result['additional_fields'] = additional_fields return result
def get_db(ctx, rd, library_id): db = ctx.get_library(rd, library_id) if db is None: raise HTTPNotFound('Library %r not found' % library_id) return db
def check(tc, val): try: return tc(val) except Exception: raise HTTPNotFound('Argument of incorrect type')
def console_print(ctx, rd): if not getattr(rd.opts, 'allow_console_print', False): raise HTTPNotFound('console printing is not allowed') shutil.copyfileobj(rd.request_body_file, sys.stdout) return ''