10-11-2022, 12:55 PM | #46 | |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Quote:
Code:
'foo' if mi.get('#custcol') is None else mi.get('#custcol') Code:
def get_default_if_none(book, field, default): v = book.get(field, None) return default if v is None else v Last edited by chaley; 10-11-2022 at 02:27 PM. |
|
10-11-2022, 12:56 PM | #47 |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
The 'context' changes are in calibre source.
|
Advert | |
|
10-12-2022, 06:35 AM | #48 |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
@capink: do you see a use for constructing your own python formatter context object and passing it to the formatter? One I thought of is what you mentioned in the development thread: adding "helper methods" to the context.
The mechanism would work like globals. If an instance isn't provided (a named parameter) then the formatter would construct one. If one is provided then it will be used, with the lifetime controlled by you. |
10-12-2022, 08:56 AM | #49 | |
Wizard
Posts: 1,092
Karma: 1948136
Join Date: Aug 2015
Device: Kindle
|
Quote:
|
|
10-12-2022, 01:52 PM | #50 | |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Moderator Notice Moved back to the development forum Quote:
If you mean both then the only mechanism that exists is the stored templates / template functions. You could add to the base functions but we would need to work out how to control their lifetime. Assuming you mean PTM templates, I think I see the problem you are solving. The context for template searches is the calibre base context, not one you created, so you have no way to put your helper functions in it. However, there is another way consistent with python and calibre: put these helper functions into their own module or own class and the PTM writer can "import" them. Doing it this way avoids monkey patching and all the lifetime and name collision problems that come with it. It also handles the problem that action chains might not be installed because the user writing the import can ensure that it is. Something like this: Code:
python: from calibre_plugins.action_chains.templates.functions import SelectionCount def evaluate(book, ctx): return '' |
|
Advert | |
|
10-12-2022, 02:10 PM | #51 |
Wizard
Posts: 1,092
Karma: 1948136
Join Date: Aug 2015
Device: Kindle
|
Lol. It is funny how simple the solution is. I wanted it for PTM, but I've been looking for solution through the lens of GPM, because that what I was struggling with for the past couple years. Thanks.
The whole PTM thing is going to open up a lot of possibilities. e.g. combining PTM with template search, you can use data from one library (authors library), to search for books in another. Which brings up the question, now that this is not implemented as a method, how can I persist the data, say for the duration of template search. Edit: The context object or the globals dict can be used for this. Last edited by capink; 10-12-2022 at 02:36 PM. |
10-12-2022, 04:33 PM | #52 | |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Quote:
The cached compiled template's lifetime is
You can also define persistent attributes in various ways then give a template access to the attributes in some way or another. As for "other possibilities", yes. A PTM template can do a lot of things, for example interact with the GUI. It can search, add and remove category items, and modify metadata; then tell the GUI to refresh. I am not convinced this is a good idea for lots of reasons, but no matter what I think the templates can do it because they have access to the calibre API, |
|
10-13-2022, 08:48 AM | #53 |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
I added the ability to use custom python context classes. The change is in calibre source.
How to use:
Code:
template = '''python: def evaluate(book, ctx): tags = ctx.db.new_api.all_field_names('tags') return ','.join(list(ctx.helper_function(tags))) ''' from calibre.utils.formatter import PythonTemplateContext class CustomContext(PythonTemplateContext): def helper_function(self, arg): s = set(arg) s.add('helper called') return s v = formatter.safe_format(template, {}, 'TEMPLATE ERROR', mi, python_context_object=CustomContext()) self.assertEqual(set(v.split(',')), {'Tag One', 'News', 'Tag Two','helper called'}) |
10-13-2022, 11:58 AM | #54 |
Wizard
Posts: 1,092
Karma: 1948136
Join Date: Aug 2015
Device: Kindle
|
This will be very useful for me.
|
10-13-2022, 12:53 PM | #55 | |
Chalut o/
Posts: 410
Karma: 145324
Join Date: Dec 2017
Device: Kobo
|
Quote:
This is no longer the case. At least not on my repositories. I have created a system that allows to easily call and use the functions currently loaded in the formatter. Voilà, voilà. @chaley, @kovidgoyal, could you check this out, thanks. I don't want to make a pull request right now because it's a big change for my knowledge and I don't want to make a big problem. (also if is need to improve the code doc) So, as it works: This adds 2 attributes at th context: .formatter and .funcs. .formatter is the current TemplateFormatter. It's good, but it's not best. There .funcs enters on stage (that's where the fun start). He will which allows you to easily call any function currently loaded in the TemplateFormatter. You use them like any other function just with the name of the one you want as an attribute (plus an _ 'underscore' at the end to avoid conflicts with Python keywords). And you don't need to handle the special arguments of these (formatter, kwargs, mi, locals | book, context), it does it all by itself, you just need to provide the yours real arguments to your function. Example: I want to convert formats_sizes() to human_readable(), easy: Code:
python: def evaluate(book, context): formats = {} for f in context.funcs.formats_sizes_().split(','): f = f.strip().split(':', 1) formats[f[0].lower()] = context.funcs.human_readable_(f[1]) return ','.join([k+':'+v for k,v in formats.items()]) And of course, user functions and stored templates can be used in the same way. (however, what was less fun was to return an exception that made sense) Last edited by un_pogaz; 10-13-2022 at 03:33 PM. |
|
10-13-2022, 04:43 PM | #56 | |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Quote:
You are adding the ability to call builtin formatter functions from python templates. Calling these functions requires you to accept that the functions return strings, even when that doesn't make sense given the underlying data. For example, by looking at the source for formats_sizes() and human_readable() (easily available), assuming I know python I see how to call the underlying db methods. Using that knowledge I can write your template as Code:
python: from calibre import human_readable def evaluate(book, context): return ','.join([k+ ':' + human_readable(v['size']) for k,v in book.get('format_metadata', {}).items()]) The same function could be written in TPM as Code:
program: sizes = formats_sizes(); result = ''; for fmt in sizes: list_split(fmt, ':', 'v'); result = list_join(',', result, ',', v_0 & ':' & human_readable(v_1), ',') rof; result ---- Another question: TPM is quite efficient because it is 'compiled' into an abstract syntax tree. A python template calling a sequence of template functions won't be a lot faster than a GPM template doing the same thing. Many of the inefficiencies of TPM come from list handing, which your proposal doesn't address. What do I gain by using PTM? Does the gain justify the ongoing maintenance cost? Another issue, probably not important: the arguments(), globals(), and set_globals() functions are implemented directly in the formatter. A user cannot call the functions in .funcs. |
|
10-14-2022, 04:55 AM | #57 | |||
Chalut o/
Posts: 410
Karma: 145324
Join Date: Dec 2017
Device: Kobo
|
Quote:
Quote:
The main goal is to have a "zero brain" import of the buildin function. It's not the "best" (I agree) but it's better than not having them at all. Quote:
The gain is very low, I agree again, but the maintenance cost too because the attributes are automatically created by the initialization of the class on the basis of the functions inside the formatter you used to initialize it (even better, I have push a version that uses __getattribute__, so it is dynamic at the change in formatter.funcs) Only some special functions (like the one you told me) need to be treated. It's a bet that there won't be another one in the future, but it doesn't represent a big problem (I work well good of the one you pointed out) Last edited by un_pogaz; 10-14-2022 at 04:58 AM. |
|||
10-14-2022, 05:37 AM | #58 | |
Grand Sorcerer
Posts: 11,742
Karma: 6997045
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Quote:
I was just writing a post to say that overnight I found convincing arguments for this ability.
I looked at your code and it is quite elegant. Some questions:
There are a few typos in comments, and a few cases where line spacing doesn't follow Kovid's rules. I can clean those up once we are satisfied with the implementation. Last edited by chaley; 10-14-2022 at 06:58 AM. Reason: Found reason for recursive __get_attr__ |
|
10-14-2022, 07:18 AM | #59 | ||||||
Chalut o/
Posts: 410
Karma: 145324
Join Date: Dec 2017
Device: Kobo
|
Quote:
Quote:
As I write this reply I am beginning to see your point and I have an idea, I will see. Quote:
Quote:
.funcs less sure. I plan to write a doc about it once the feature is stable. Quote:
Quote:
But like in any other program. If you're in python mode, it's either a notion you know, or you learn it very quickly . The main goal is the buildin and user function, therefore, little risk. And then, a reccursive call, it is rarely done by "accidents". EDIT: And a found a way to clean the list of native attribute. Now, all is inside __get_attribute__. A also fix globals(), arguments() (which returns dict) and set_globals(), character(). Push on my github Last edited by un_pogaz; 10-14-2022 at 09:55 AM. |
||||||
10-15-2022, 05:07 PM | #60 |
Wizard
Posts: 1,092
Karma: 1948136
Join Date: Aug 2015
Device: Kindle
|
I am able to pass my custom python context. Problem is, I need to be able to pass it to the template dialog as well (like we do with global_vars), otherwise, any helper functions or attributes will fail in the template dialog.
Unfortunately, I didn't have the time to test this before the weekend. |
|
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
Python functions in database and calibre 5 | Terisa de morgan | Calibre | 7 | 09-27-2020 02:52 AM |
A little help with template functions in a composite column, please! | mopkin | Library Management | 2 | 11-05-2019 11:07 PM |
Using built-in template functions in a custom template function | ilovejedd | Library Management | 4 | 01-28-2018 12:20 PM |
Rules, templates, and functions for column coloring and composed icons | readin | Library Management | 7 | 08-11-2016 04:41 PM |
template: if one of the tag is something... maybe contains or in_list functions | fxp33 | Calibre | 4 | 07-19-2014 05:18 AM |