Author_sort in author table changes

This commit is contained in:
Charles Haley 2010-06-13 22:58:46 +01:00
parent 7bf0c77dff
commit 669dd8024c
11 changed files with 242 additions and 58 deletions

View File

@ -13,7 +13,7 @@ from PyQt4.Qt import QPixmap, SIGNAL
from calibre.gui2 import choose_images, error_dialog
from calibre.gui2.convert.metadata_ui import Ui_Form
from calibre.ebooks.metadata import authors_to_string, string_to_authors, \
MetaInformation, authors_to_sort_string
MetaInformation
from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.convert import Widget
@ -57,7 +57,7 @@ class MetadataWidget(Widget, Ui_Form):
au = unicode(self.author.currentText())
au = re.sub(r'\s+et al\.$', '', au)
authors = string_to_authors(au)
self.author_sort.setText(authors_to_sort_string(authors))
self.author_sort.setText(self.db.author_sort_from_authors(authors))
def initialize_metadata_options(self):

View File

@ -23,7 +23,7 @@ from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
pixmap_to_data, warning_dialog, \
question_dialog, info_dialog, choose_dir
from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string
from calibre.ebooks.metadata import authors_to_string
from calibre import preferred_encoding, prints
from calibre.utils.filenames import ascii_filename
from calibre.devices.errors import FreeSpaceError
@ -1409,7 +1409,7 @@ class DeviceMixin(object): # {{{
# Set author_sort if it isn't already
asort = getattr(book, 'author_sort', None)
if not asort and book.authors:
book.author_sort = authors_to_sort_string(book.authors)
book.author_sort = self.db.author_sort_from_authors(book.authors)
resend_metadata = True
if resend_metadata:

View File

@ -8,7 +8,7 @@ from PyQt4.QtGui import QDialog, QGridLayout
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.ebooks.metadata import string_to_authors, authors_to_sort_string, \
from calibre.ebooks.metadata import string_to_authors, \
authors_to_string
from calibre.gui2.custom_column_widgets import populate_bulk_metadata_page
@ -110,10 +110,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
au = string_to_authors(au)
self.db.set_authors(id, au, notify=False)
if self.auto_author_sort.isChecked():
aut = self.db.authors(id, index_is_id=True)
aut = aut if aut else ''
aut = [a.strip().replace('|', ',') for a in aut.strip().split(',')]
x = authors_to_sort_string(aut)
x = self.db.author_sort_from_book(id, index_is_id=True)
if x:
self.db.set_author_sort(id, x, notify=False)
aus = unicode(self.author_sort.text())

View File

@ -23,7 +23,7 @@ from calibre.gui2.dialogs.fetch_metadata import FetchMetadata
from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.gui2.widgets import ProgressIndicator
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.metadata import authors_to_sort_string, string_to_authors, \
from calibre.ebooks.metadata import string_to_authors, \
authors_to_string, check_isbn
from calibre.ebooks.metadata.library_thing import cover_from_isbn
from calibre import islinux, isfreebsd
@ -459,7 +459,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
au = unicode(self.authors.text())
au = re.sub(r'\s+et al\.$', '', au)
authors = string_to_authors(au)
self.author_sort.setText(authors_to_sort_string(authors))
self.author_sort.setText(self.db.author_sort_from_authors(authors))
def swap_title_author(self):
title = self.title.text()

View File

@ -0,0 +1,16 @@
#!/usr/bin/env python
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__license__ = 'GPL v3'
from PyQt4.Qt import QDialog
from calibre.gui2.dialogs.sort_field_dialog_ui import Ui_SortFieldDialog
class SortFieldDialog(QDialog, Ui_SortFieldDialog):
def __init__(self, parent, text):
QDialog.__init__(self, parent)
Ui_SortFieldDialog.__init__(self)
self.setupUi(self)
if text is not None:
self.textbox.setText(text)

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SortFieldDialog</class>
<widget class="QDialog" name="SortFieldDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>334</width>
<height>135</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Edit sort field</string>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>311</width>
<height>111</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLineEdit" name="textbox"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SortFieldDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>229</x>
<y>211</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>234</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SortFieldDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>297</x>
<y>217</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>234</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -23,6 +23,7 @@ from calibre.utils.search_query_parser import saved_searches
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.tag_categories import TagCategories
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
from calibre.gui2.dialogs.sort_field_dialog import SortFieldDialog
class TagDelegate(QItemDelegate):
@ -90,6 +91,7 @@ class TagsView(QTreeView): # {{{
user_category_edit = pyqtSignal(object)
tag_list_edit = pyqtSignal(object, object)
saved_search_edit = pyqtSignal(object)
author_sort_edit = pyqtSignal(object, object, object)
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
@ -173,6 +175,9 @@ class TagsView(QTreeView): # {{{
if action == 'manage_searches':
self.saved_search_edit.emit(category)
return
if action == 'edit_author_sort':
self.author_sort_edit.emit(self, category, index)
return
if action == 'hide':
self.hidden_categories.add(category)
elif action == 'show':
@ -193,6 +198,8 @@ class TagsView(QTreeView): # {{{
if item.type == TagTreeItem.TAG:
tag_item = item
tag_name = item.tag.name
tag_id = item.tag.id
tag_sort = item.tag.sort
item = item.parent
if item.type == TagTreeItem.CATEGORY:
category = unicode(item.name.toString())
@ -211,6 +218,10 @@ class TagsView(QTreeView): # {{{
self.context_menu.addAction(_('Rename') + " '" + tag_name + "'",
partial(self.context_menu_handler, action='edit_item',
category=tag_item, index=index))
if key == 'authors':
self.context_menu.addAction(_('Edit sort for') + " '" + tag_name + "'",
partial(self.context_menu_handler, action='edit_author_sort',
category=tag_sort, index=tag_id))
self.context_menu.addSeparator()
# Hide/Show/Restore categories
self.context_menu.addAction(_('Hide category %s') % category,
@ -684,6 +695,7 @@ class TagBrowserMixin(object): # {{{
self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
self.tags_view.author_sort_edit.connect(self.do_author_sort_edit)
self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
self.edit_categories.clicked.connect(lambda x:
@ -713,6 +725,15 @@ class TagBrowserMixin(object): # {{{
self.saved_search.clear_to_help()
self.search.clear_to_help()
def do_author_sort_edit(self, parent, text, id):
editor = SortFieldDialog(parent, text)
d = editor.exec_()
if d:
print editor.textbox.text()
self.library_view.model().db.set_sort_field_for_author \
(id, unicode(editor.textbox.text()))
self.tags_view.recount()
# }}}
class TagBrowserWidget(QWidget): # {{{

View File

@ -12,7 +12,7 @@ from math import floor
from PyQt4.QtGui import QImage
from calibre.ebooks.metadata import title_sort
from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.library.database import LibraryDatabase
from calibre.library.field_metadata import FieldMetadata, TagsIcons
from calibre.library.schema_upgrades import SchemaUpgrade
@ -20,7 +20,7 @@ from calibre.library.caches import ResultCache
from calibre.library.custom_columns import CustomColumns
from calibre.library.sqlite import connect, IntegrityError, DBThread
from calibre.ebooks.metadata import string_to_authors, authors_to_string, \
MetaInformation, authors_to_sort_string
MetaInformation
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
from calibre.ptempfile import PersistentTemporaryFile
@ -56,12 +56,14 @@ copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
class Tag(object):
def __init__(self, name, id=None, count=0, state=0, avg=0, tooltip=None, icon=None):
def __init__(self, name, id=None, count=0, state=0, avg=0, sort=None,
tooltip=None, icon=None):
self.name = name
self.id = id
self.count = count
self.state = state
self.avg_rating = avg/2.0 if avg is not None else 0
self.sort = sort
self.tooltip = tooltip
self.icon = icon
@ -135,7 +137,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
id,
name,
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count,
(0) as avg_rating
(0) as avg_rating,
(null) as sort
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@ -147,7 +150,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
id,
name,
(SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count,
(0) as avg_rating
(0) as avg_rating,
(null) as sort
FROM tags as x WHERE name!="{0}" AND id IN
(SELECT DISTINCT tag FROM books_tags_link WHERE book IN
(SELECT DISTINCT book FROM books_tags_link WHERE tag IN
@ -425,6 +429,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if aum: aum = [a.strip().replace('|', ',') for a in aum.split(',')]
mi = MetaInformation(self.title(idx, index_is_id=index_is_id), aum)
mi.author_sort = self.author_sort(idx, index_is_id=index_is_id)
mi.authors_sort_strings = self.authors_sort_strings(idx, index_is_id)
mi.comments = self.comments(idx, index_is_id=index_is_id)
mi.publisher = self.publisher(idx, index_is_id=index_is_id)
mi.timestamp = self.timestamp(idx, index_is_id=index_is_id)
@ -701,12 +706,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
continue
cn = cat['column']
if ids is None:
query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_{1}'.format(cn, tn)
query = '''SELECT id, {0}, count, avg_rating, sort
FROM tag_browser_{1}'''.format(cn, tn)
else:
query = 'SELECT id, {0}, count, avg_rating FROM tag_browser_filtered_{1}'.format(cn, tn)
query = '''SELECT id, {0}, count, avg_rating
FROM tag_browser_filtered_{1}'''.format(cn, tn)
if sort_on_count:
query += ' ORDER BY count DESC'
else:
if 'category_sort' in cat:
cn = cat['category_sort']
query += ' ORDER BY {0} ASC'.format(cn)
data = self.conn.get(query)
@ -736,7 +745,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
formatter = (lambda x:unicode(x))
categories[category] = [Tag(formatter(r[1]), count=r[2], id=r[0],
avg=r[3], icon=icon, tooltip=tooltip)
avg=r[3], sort=r[4],
icon=icon, tooltip=tooltip)
for r in data if item_not_zero_func(r)]
if category == 'series' and not sort_on_count:
if tweaks['title_series_sorting'] == 'library_order':
@ -912,6 +922,38 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.set_path(id, True)
self.notify('metadata', [id])
# Given a book, return the list of author sort strings for the book's authors
def authors_sort_strings(self, id, index_is_id=False):
id = id if index_is_id else self.id(id)
aut_strings = self.conn.get('''
SELECT sort
FROM authors, books_authors_link as bl
WHERE bl.book=? and authors.id=bl.author
ORDER BY bl.id''', (id,))
result = []
for (sort,) in aut_strings:
result.append(sort)
return result
# Given a book, return the author_sort string for authors of the book
def author_sort_from_book(self, id, index_is_id=False):
auts = self.authors_sort_strings(id, index_is_id)
return ' & '.join(auts).replace('|', ',')
# Given a list of authors, return the author_sort string for the authors,
# preferring the author sort associated with the author over the computed
# string
def author_sort_from_authors(self, authors):
result = []
for aut in authors:
aut = aut.replace(',', '|')
r = self.conn.get('SELECT sort FROM authors WHERE name=?', (aut,), all=False)
if r is None:
result.append(author_to_author_sort(aut))
else:
result.append(r)
return ' & '.join(result).replace('|', ',')
def set_authors(self, id, authors, notify=True):
'''
`authors`: A list of authors.
@ -938,7 +980,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
(id, aid))
except IntegrityError: # Sometimes books specify the same author twice in their metadata
pass
ss = authors_to_sort_string(authors)
self.conn.commit()
ss = self.author_sort_from_book(id, index_is_id=True)
self.conn.execute('UPDATE books SET author_sort=? WHERE id=?',
(ss, id))
self.conn.commit()
@ -1117,7 +1160,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.commit()
# There is no editor for author, so we do not need get_authors_with_ids or
# delete_author_using_id.
# delete_author_using_id. However, we can change the author's sort field, so
# we provide that setter
def set_sort_field_for_author(self, old_id, new_sort):
self.conn.execute('UPDATE authors SET sort=? WHERE id=?', \
(new_sort, old_id))
self.conn.commit()
def rename_author(self, old_id, new_name):
# Make sure that any commas in new_name are changed to '|'!
@ -1187,22 +1236,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# now fix the filesystem paths
self.set_path(book_id, index_is_id=True)
# Next fix the author sort. Reset it to the default
authors = self.conn.get('''
SELECT authors.name
FROM authors, books_authors_link as bl
WHERE bl.book = ? and bl.author = authors.id
ORDER BY bl.id
''' , (book_id,))
# unpack the double-list structure
for i,aut in enumerate(authors):
authors[i] = aut[0]
ss = authors_to_sort_string(authors)
# Change the '|'s to ','
ss = ss.replace('|', ',')
self.conn.execute('''UPDATE books
SET author_sort=?
WHERE id=?''', (ss, book_id))
self.conn.commit()
ss = self.author_sort_from_book(book_id, index_is_id=True)
self.set_author_sort(book_id, ss)
# the caller will do a general refresh, so we don't need to
# do one here
@ -1439,7 +1474,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if not add_duplicates and self.has_book(mi):
return None
series_index = 1.0 if mi.series_index is None else mi.series_index
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
title = mi.title
if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace')
@ -1479,7 +1514,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
duplicates.append((path, format, mi))
continue
series_index = 1.0 if mi.series_index is None else mi.series_index
aus = mi.author_sort if mi.author_sort else ', '.join(mi.authors)
aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
title = mi.title
if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace')
@ -1518,7 +1553,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.title = _('Unknown')
if not mi.authors:
mi.authors = [_('Unknown')]
aus = mi.author_sort if mi.author_sort else authors_to_sort_string(mi.authors)
aus = mi.author_sort if mi.author_sort else self.author_sort_from_authors(mi.authors)
if isinstance(aus, str):
aus = aus.decode(preferred_encoding, 'replace')
title = mi.title if isinstance(mi.title, unicode) else \

View File

@ -72,7 +72,8 @@ class FieldMetadata(dict):
'name':_('Authors'),
'search_terms':['authors', 'author'],
'is_custom':False,
'is_category':True}),
'is_category':True,
'category_sort':'sort'}),
('series', {'table':'series',
'column':'name',
'link_column':'series',

View File

@ -295,7 +295,8 @@ class SchemaUpgrade(object):
def upgrade_version_11(self):
'Add average rating to tag browser views'
def create_std_tag_browser_view(table_name, column_name, view_column_name):
def create_std_tag_browser_view(table_name, column_name,
view_column_name, sort_column_name):
script = ('''
DROP VIEW IF EXISTS tag_browser_{tn};
CREATE VIEW tag_browser_{tn} AS SELECT
@ -305,22 +306,25 @@ class SchemaUpgrade(object):
(SELECT AVG(ratings.rating)
FROM books_{tn}_link as tl, books_ratings_link as bl, ratings
WHERE tl.{cn}={tn}.id and bl.book=tl.book and
ratings.id = bl.rating and ratings.rating <> 0) avg_rating
ratings.id = bl.rating and ratings.rating <> 0) avg_rating,
{scn} as sort
FROM {tn};
DROP VIEW IF EXISTS tag_browser_filtered_{tn};
CREATE VIEW tag_browser_filtered_{tn} AS SELECT
id,
{vcn},
{vcn} as sort,
(SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE
{cn}={tn}.id AND books_list_filter(book)) count,
(SELECT AVG(ratings.rating)
FROM books_{tn}_link as tl, books_ratings_link as bl, ratings
WHERE tl.{cn}={tn}.id and bl.book=tl.book and
ratings.id = bl.rating and ratings.rating <> 0 AND
books_list_filter(bl.book)) avg_rating
books_list_filter(bl.book)) avg_rating,
{scn} as sort
FROM {tn};
'''.format(tn=table_name, cn=column_name, vcn=view_column_name))
'''.format(tn=table_name, cn=column_name,
vcn=view_column_name, scn= sort_column_name))
self.conn.executescript(script)
def create_cust_tag_browser_view(table_name, link_table_name):
@ -335,7 +339,8 @@ class SchemaUpgrade(object):
books_ratings_link as bl,
ratings as r
WHERE {lt}.value={table}.id and bl.book={lt}.book and
r.id = bl.rating and r.rating <> 0) avg_rating
r.id = bl.rating and r.rating <> 0) avg_rating,
value as sort
FROM {table};
DROP VIEW IF EXISTS tag_browser_filtered_{table};
@ -350,20 +355,21 @@ class SchemaUpgrade(object):
ratings as r
WHERE {lt}.value={table}.id AND bl.book={lt}.book AND
r.id = bl.rating AND r.rating <> 0 AND
books_list_filter(bl.book)) avg_rating
books_list_filter(bl.book)) avg_rating,
value as sort
FROM {table};
'''.format(lt=link_table_name, table=table_name)
self.conn.executescript(script)
STANDARD_TAG_BROWSER_TABLES = [
('authors', 'author', 'name'),
('publishers', 'publisher', 'name'),
('ratings', 'rating', 'rating'),
('series', 'series', 'name'),
('tags', 'tag', 'name'),
('authors', 'author', 'name', 'sort'),
('publishers', 'publisher', 'name', 'name'),
('ratings', 'rating', 'rating', 'rating'),
('series', 'series', 'name', 'name'),
('tags', 'tag', 'name', 'name'),
]
for table, column, view_column in STANDARD_TAG_BROWSER_TABLES:
create_std_tag_browser_view(table, column, view_column)
for table, column, view_column, sort_column in STANDARD_TAG_BROWSER_TABLES:
create_std_tag_browser_view(table, column, view_column, sort_column)
db_tables = self.conn.get('''SELECT name FROM sqlite_master
WHERE type='table'
@ -374,5 +380,28 @@ class SchemaUpgrade(object):
for table in tables:
link_table = 'books_%s_link'%table
if table.startswith('custom_column_') and link_table in tables:
print table
create_cust_tag_browser_view(table, link_table)
create_cust_tag_browser_view(table, link_table)
from calibre.ebooks.metadata import author_to_author_sort
aut = self.conn.get('SELECT id, name FROM authors');
records = []
for (id, author) in aut:
records.append((id, author.replace('|', ',')))
for id,author in records:
self.conn.execute('UPDATE authors SET sort=? WHERE id=?',
(author_to_author_sort(author.replace('|', ',')).strip(), id))
self.conn.commit()
self.conn.executescript('''
CREATE TRIGGER author_insert_trg
AFTER INSERT ON authors
BEGIN
UPDATE authors SET sort=author_to_author_sort(NEW.name) WHERE id=NEW.id;
END;
CREATE TRIGGER author_update_trg
BEFORE UPDATE ON authors
BEGIN
UPDATE authors SET sort=author_to_author_sort(NEW.name)
WHERE id=NEW.id and name <> NEW.name;
END;
''')

View File

@ -14,7 +14,7 @@ from Queue import Queue
from threading import RLock
from datetime import datetime
from calibre.ebooks.metadata import title_sort
from calibre.ebooks.metadata import title_sort, author_to_author_sort
from calibre.utils.config import tweaks
from calibre.utils.date import parse_date, isoformat
@ -120,6 +120,8 @@ class DBThread(Thread):
self.conn.create_function('title_sort', 1, title_sort)
else:
self.conn.create_function('title_sort', 1, lambda x:x)
self.conn.create_function('author_to_author_sort', 1,
lambda x: author_to_author_sort(x.replace('|', ',')))
self.conn.create_function('uuid4', 0, lambda : str(uuid.uuid4()))
# Dummy functions for dynamically created filters
self.conn.create_function('books_list_filter', 1, lambda x: 1)