|  12-24-2022, 06:20 PM | #1 | 
| Deviser            Posts: 2,265 Karma: 2090983 Join Date: Aug 2013 Location: Texas Device: none | 
				
				Running PTM/PythonTemplateContexts in Job, not GUI
			 
			
			I saved this PTM template in Preferences > Template Functions: Spoiler: 
 The globals dict was of course empty since it gets filled at runtime, so an error message about the key, 'val', missing was spurious. I still was able to save & apply the PTM template. I then ran the above PTM template in a plugin's Job using this code: Code:  
        funcobject = user_template_functions['daltonst1']
        func_name = funcobject.name
        formatter = TemplateFormatter()
        formatter.funcs.update(formatter_functions()._functions_from_library)
        ptc = PythonTemplateContext()
        ptc.funcs = formatter_functions()._functions_from_library
        if func_name in ptc.funcs:
            func = ptc.funcs[func_name]
        elif func_name in formatter.funcs:
            func = formatter.funcs[func_name]
        else:
            func = funcobject 
        ptc.func = func
        ptc.func_name = func_name
        ptc.name = func_name
        ptc.globals = {}
        ptc.globals['val'] = "ABCDEFGHIJK9999999999MNOPQRSTUVWXYZ"
        ptc.arguments = ""
        ptc.db = guidb
        mi = guidb.new_api.get_metadata(current_book)
        ptc.book = mi
        ptc.formatter = formatter
        formatter.python_context_object = ptc
        formatter.func = func
        formatter.func_name = func_name
        formatter.name = func_name
        formatter.db = guidb
        formatter.mi = mi
        formatter.book = mi
        formatter.kwargs = None
        ffc = FormatterFuncsCaller(formatter)
        call = ffc.__getattribute__(func_name)
        output_text = call(formatter.python_context_object)Spoiler: 
 I went back into where I saved this PTM template in Preferences > Template Functions to work on the same PTM template, but the GUI raised the error shown below, and would not let me back into Preferences > Template Functions to work on the PTM template to fix the error (Catch-22): Spoiler: 
 Since I could not use Preferences > Template Functions any more, at all, I went into metadata.db, table Preferences, key 'user_template_functions', and carved out the entry for my PTM template from the JSON: Spoiler: 
 I then ran Calibre, and could finally enter into Preferences > Template Functions since the PTM template in question no longer existed. ---------------------------------------------------------------------------------------------- Question: Can the PTM template cache get poisoned when the PTM template is run "in batch" via a Job? If so, is there a way to clear it so the template_dialog for Preferences > Template Functions can always be successfully executed? Question: How do I create a PTM template using a dict with keys that only exist at runtime? Question: Is this syntax supported: text = context.globals['val'] ? If not, what is the proper way to accomplish passing a single string value to a PTM template at runtime? Thank you. Happy/Merry Christmas. DaltonST | 
|   |   | 
|  12-25-2022, 01:28 PM | #2 | ||||
| Grand Sorcerer            Posts: 12,525 Karma: 8065948 Join Date: Jan 2010 Location: Notts, England Device: Kobo Libra 2 | Quote: 
 The error indicates that the name of a stored template changed from a string to a list. I don't see any way that this can happen in base calibre. It is hard to follow the effects of your code as it modifies internal data structures. At a minimum I suspect possible threading problems. On this topic, I wonder why you are reaching so far into the guts of the formatter to run your template? It is very hard to predict what will happen, given that the state save/restore mechanism is being bypassed and base calibre data structures are being modified. Why not use safe_format(), which is how templates are supposed to be called? Or unsafe_format() for that matter? If you want to call a stored template then call safe_format() with something like Code: program: stored_template_name() Quote: 
 Code:     context.globals.get('dict_name', {}).get('key_name', None)Quote: 
 Code:     text = context.globals.get('val', whatever_default_you_want)Code: program:
    some_python_template(some_argument)Quote: 
 | ||||
|   |   | 
| Advert | |
|  | 
|  12-26-2022, 06:34 AM | #3 | 
| Grand Sorcerer            Posts: 12,525 Karma: 8065948 Join Date: Jan 2010 Location: Notts, England Device: Kobo Libra 2 | 
			
			I don't know if this is of any use to you, but here is how the Action Chains plugin invokes templates, with hopes that @capink doesn't mind me copying his code here. I picked this because it uses all of the template types, attributes in the python context, plugin-defined template functions, and user-provided templates and functions. The main invocation function: Code: from calibre.ebooks.metadata.book.formatter import SafeFormat
[...]
class ACTemplateContext(PythonTemplateContext):
    pass
def get_template_output(template, kwargs, TEMPLATE_ERROR, book, global_vars={},
                        template_functions=None, context_attrs={}):
    if not template_functions:
        template_functions = get_template_functions[0]
    try:
        # calibre >= 6.7.0. support for python template context object
        context = ACTemplateContext()
        context.set_values(**context_attrs)
        output = SafeFormat().safe_format(template, kwargs, TEMPLATE_ERROR, book,
                                          global_vars=global_vars,
                                          template_functions=template_functions,
                                          python_context_object=context)
    except:
        output = SafeFormat().safe_format(template, kwargs, TEMPLATE_ERROR, book,
                                          global_vars=global_vars,
                                          template_functions=template_functions)
    return outputCode: def get_template_functions(plugin_action):
    all_functions = OrderedDict()
    builtin_functions = OrderedDict()
    for cls in BUILTIN_FUNCTIONS:
        builtin_functions[cls.name] = cls
    imported_functions = get_imported_functions(plugin_action)
    user_functions = get_user_functions(plugin_action)
    
    action_functions = get_action_functions(plugin_action)
    
    calibre_funcs = formatter_functions().get_functions()
    # Note: imported functions can either be class or instantiated object
    for function_name, function_obj in imported_functions.items():
        # dont override builtin functions
        if function_name in builtin_functions.keys():
            continue
        try:
            if isinstance(function_obj, (TemplateFunction, FormatterFunction)):
                function = function_obj
            elif issubclass(function_obj, TemplateFunction):
                function = function_obj(plugin_action)
            elif issubclass(function_obj, FormatterFunction):
                function = function_obj()
            all_functions[function_name] = function
        except TypeError as e:
            # TypeError: issubclass() arg 1 must be a class
            import traceback
            if DEBUG:
                prints('Action Chains: Error intializing imported function: Un-reconized object: {}\n{}'.format(function_obj, traceback.format_exc()))            
        except Exception as e:
            import traceback
            if DEBUG:
                prints('Action Chains: Error intializing imported function: {}\n{}'.format(function_name, traceback.format_exc()))
    for function_name, cls in user_functions.items():
        try:
            function = cls(plugin_action)
            all_functions[function_name] = function
        except Exception as e:
            import traceback
            if DEBUG:
                prints('Action Chains: Error intializing user function: {}\n{}'.format(function_name, traceback.format_exc()))
    for function_name, function in action_functions.items():
        try:
            all_functions[function_name] = function
        except Exception as e:
            import traceback
            if DEBUG:
                prints('Action Chains: Error intializing action function: {}\n{}'.format(function_name, traceback.format_exc()))
    for name, func in calibre_funcs.items():
        all_functions[name] = func
    # we keep this last in case we want to override some calibre formatter functions
    for function_name, cls in builtin_functions.items():
        function = cls(plugin_action)
        all_functions[function_name] = function
    return all_functions, builtin_functions, user_functions, action_functions, imported_functions | 
|   |   | 
|  12-26-2022, 08:30 AM | #4 | |
| Wizard            Posts: 1,216 Karma: 1995558 Join Date: Aug 2015 Device: Kindle | Quote: 
 The code is as much yours as it is mine. You are welcome to do with it as you please. I am grateful and indebted to you not just for your work on templates and other areas of calibre, but also for your kind support whenever we ask for it (and sometimes when we don't even ask). Happy holidays and merry Christmas to you.   | |
|   |   | 
|  | 
| 
 | 
|  Similar Threads | ||||
| Thread | Thread Starter | Forum | Replies | Last Post | 
| [GUI Plugin] Job Spy | DaltonST | Plugins | 1122 | 03-05-2025 10:43 AM | 
| Calibredb not working when GUI is running | webdjoe | Calibre | 9 | 07-26-2017 04:29 AM | 
| Running job on library change | davidfor | Development | 2 | 11-26-2013 06:26 AM | 
| Ubuntu/Linux : Command to schedule a job with Calibre.( No GUI ) | DurgaPrasad | Calibre | 0 | 10-16-2013 06:50 AM | 
| running mobi2mobi (cmd or GUI) | scotchirish | Kindle Formats | 2 | 05-17-2011 09:04 AM |