When sorting fails first try fallback by sorting bad values as the first value

This is closer to the python 2 behavior
This commit is contained in:
Kovid Goyal 2020-10-21 09:55:10 +05:30
parent 8b3b008c3c
commit 7fe84df779
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 56 additions and 20 deletions

View File

@ -6,36 +6,45 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import os, traceback, random, shutil, operator, sys import operator
import os
import random
import shutil
import sys
import traceback
from collections import MutableSet, Set, defaultdict
from functools import partial, wraps
from io import BytesIO from io import BytesIO
from collections import defaultdict, Set, MutableSet
from functools import wraps, partial
from polyglot.builtins import iteritems, itervalues, unicode_type, zip, string_or_bytes, cmp
from time import time from time import time
from calibre import isbytestring, as_unicode from calibre import as_unicode, isbytestring
from calibre.constants import iswindows, preferred_encoding from calibre.constants import iswindows, preferred_encoding
from calibre.customize.ui import run_plugins_on_import, run_plugins_on_postimport, run_plugins_on_postadd from calibre.customize.ui import (
run_plugins_on_import, run_plugins_on_postadd, run_plugins_on_postimport
)
from calibre.db import SPOOL_SIZE, _get_next_series_num_for_list from calibre.db import SPOOL_SIZE, _get_next_series_num_for_list
from calibre.db.annotations import merge_annotations from calibre.db.annotations import merge_annotations
from calibre.db.categories import get_categories from calibre.db.categories import get_categories
from calibre.db.locking import create_locks, DowngradeLockError, SafeReadLock from calibre.db.errors import NoSuchBook, NoSuchFormat
from calibre.db.errors import NoSuchFormat, NoSuchBook from calibre.db.fields import IDENTITY, InvalidLinkTable, create_field
from calibre.db.fields import create_field, IDENTITY, InvalidLinkTable from calibre.db.lazy import FormatMetadata, FormatsList, ProxyMetadata
from calibre.db.locking import DowngradeLockError, SafeReadLock, create_locks
from calibre.db.search import Search from calibre.db.search import Search
from calibre.db.tables import VirtualTable from calibre.db.tables import VirtualTable
from calibre.db.utils import type_safe_sort_key_function
from calibre.db.write import get_series_values, uniq from calibre.db.write import get_series_values, uniq
from calibre.db.lazy import FormatMetadata, FormatsList, ProxyMetadata
from calibre.ebooks import check_ebook_format from calibre.ebooks import check_ebook_format
from calibre.ebooks.metadata import string_to_authors, author_to_author_sort from calibre.ebooks.metadata import author_to_author_sort, string_to_authors
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ptempfile import (base_dir, PersistentTemporaryFile, from calibre.ptempfile import PersistentTemporaryFile, SpooledTemporaryFile, base_dir
SpooledTemporaryFile)
from calibre.utils.config import prefs, tweaks from calibre.utils.config import prefs, tweaks
from calibre.utils.date import now as nowf, utcnow, UNDEFINED_DATE from calibre.utils.date import UNDEFINED_DATE, now as nowf, utcnow
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.localization import canonicalize_lang from calibre.utils.localization import canonicalize_lang
from polyglot.builtins import (
cmp, iteritems, itervalues, string_or_bytes, unicode_type, zip
)
def api(f): def api(f):
@ -975,12 +984,17 @@ class Cache(object):
fields = uniq(fields, operator.itemgetter(0)) fields = uniq(fields, operator.itemgetter(0))
if len(fields) == 1: if len(fields) == 1:
keyfunc = sort_key_func(fields[0][0])
reverse = not fields[0][1]
try: try:
return sorted(ids_to_sort, key=sort_key_func(fields[0][0]), return sorted(ids_to_sort, key=keyfunc, reverse=reverse)
reverse=not fields[0][1])
except Exception as err: except Exception as err:
print('Failed to sort database on field:', fields[0][0], 'with error:', err, file=sys.stderr) print('Failed to sort database on field:', fields[0][0], 'with error:', err, file=sys.stderr)
return sorted(ids_to_sort, reverse=not fields[0][1]) try:
return sorted(ids_to_sort, key=type_safe_sort_key_function(keyfunc), reverse=reverse)
except Exception as err:
print('Failed to type-safe sort database on field:', fields[0][0], 'with error:', err, file=sys.stderr)
return sorted(ids_to_sort, reverse=reverse)
sort_key_funcs = tuple(sort_key_func(field) for field, order in fields) sort_key_funcs = tuple(sort_key_func(field) for field, order in fields)
orders = tuple(1 if order else -1 for _, order in fields) orders = tuple(1 if order else -1 for _, order in fields)
Lazy = object() # Lazy load the sort keys for sub-sort fields Lazy = object() # Lazy load the sort keys for sub-sort fields
@ -2204,9 +2218,9 @@ class Cache(object):
def embed_metadata(self, book_ids, only_fmts=None, report_error=None, report_progress=None): def embed_metadata(self, book_ids, only_fmts=None, report_error=None, report_progress=None):
''' Update metadata in all formats of the specified book_ids to current metadata in the database. ''' ''' Update metadata in all formats of the specified book_ids to current metadata in the database. '''
field = self.fields['formats'] field = self.fields['formats']
from calibre.ebooks.metadata.opf2 import pretty_print
from calibre.customize.ui import apply_null_metadata from calibre.customize.ui import apply_null_metadata
from calibre.ebooks.metadata.meta import set_metadata from calibre.ebooks.metadata.meta import set_metadata
from calibre.ebooks.metadata.opf2 import pretty_print
if only_fmts: if only_fmts:
only_fmts = {f.lower() for f in only_fmts} only_fmts = {f.lower() for f in only_fmts}
@ -2356,8 +2370,8 @@ class Cache(object):
@write_api @write_api
def restore_annotations(self, book_id, annotations): def restore_annotations(self, book_id, annotations):
from calibre.utils.iso8601 import parse_iso8601
from calibre.utils.date import EPOCH from calibre.utils.date import EPOCH
from calibre.utils.iso8601 import parse_iso8601
umap = defaultdict(list) umap = defaultdict(list)
for adata in annotations: for adata in annotations:
key = adata['user_type'], adata['user'], adata['format'] key = adata['user_type'], adata['user'], adata['format']
@ -2373,8 +2387,8 @@ class Cache(object):
@write_api @write_api
def merge_annotations_for_book(self, book_id, fmt, annots_list, user_type='local', user='viewer'): def merge_annotations_for_book(self, book_id, fmt, annots_list, user_type='local', user='viewer'):
from calibre.utils.iso8601 import parse_iso8601
from calibre.utils.date import EPOCH from calibre.utils.date import EPOCH
from calibre.utils.iso8601 import parse_iso8601
amap = self._annotations_map_for_book(book_id, fmt, user_type=user_type, user=user) amap = self._annotations_map_for_book(book_id, fmt, user_type=user_type, user=user)
merge_annotations(annots_list, amap) merge_annotations(annots_list, amap)
alist = [] alist = []

View File

@ -405,3 +405,25 @@ def atof(string):
d = d.decode('utf-8', 'ignore') or '.' d = d.decode('utf-8', 'ignore') or '.'
number_separators = t, d number_separators = t, d
return float(string.replace(number_separators[1], '.').replace(number_separators[0], '')) return float(string.replace(number_separators[1], '.').replace(number_separators[0], ''))
def type_safe_sort_key_function(keyfunc=None):
if keyfunc is None:
keyfunc = lambda x: x
sentinel = object()
first_value = sentinel
def key(x):
nonlocal first_value
ans = keyfunc(x)
if first_value is sentinel:
first_value = ans
else:
try:
ans < first_value
first_value < ans
except TypeError:
ans = first_value
return ans
return key