Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup flow when registering new template handler #745

Open
TomFreudenberg opened this issue Feb 15, 2025 · 2 comments
Open

Setup flow when registering new template handler #745

TomFreudenberg opened this issue Feb 15, 2025 · 2 comments

Comments

@TomFreudenberg
Copy link
Contributor

TomFreudenberg commented Feb 15, 2025

Hi @derks

I created a new template handler as extension. Inside the extension there is my load() to register.

I shortened the source for the issue ...

class TokeoJinja2TemplateHandler(Jinja2TemplateHandler):

    class Meta(TemplateHandler.Meta):
        """Handler meta-data."""

        label = 'tokeo.jinja2'

        #: Id for config
        config_section = 'jinja2'

        #: Configuration default values
        config_defaults = {
            'template_dirs': [],
        }

    def __init__(self, *args, **kw):
        super(TokeoJinja2TemplateHandler, self).__init__(*args, **kw)

    def _setup(self, app):
        # save pointer to app
        self.app = app
        # prepare the config
        self.app.config.merge({self._meta.config_section: self._meta.config_defaults}, override=False)
        # extend the template dirs if exist
        d = self.app.config.get(self._meta.config_section, 'template_dirs')
        for p in d:
            app.add_template_dir(p)


def load(app):
    app.handler.register(TokeoJinja2TemplateHandler)
    app._meta.template_handler = TokeoJinja2TemplateHandler.Meta.label

The _setup is now already called on foundation.py

Image

already on self._setup_extension_handler() and not at self._setup_template_handler()

In this case the config is not yet upset and my app raises an error

because self._meta.template_dirs in add_template_dir stil None.


How to register a template handler from an extension, that will run it's setup at the right place?

Thanks for your hint

@TomFreudenberg
Copy link
Contributor Author

The issue here is:

  1. main.py -> extensions = [ ..., tokeo.ext.jinja2, ... ]

In case of activating the extension, the module is loaded and load() will be called.

There I add the new Handler by app.handler.register(TokeoJinja2TemplateHandler)

Now it's _setup() get's already called on _setup_extension_handler

But while the Handler is derrived from an Output/TemplateHandler it's _setup had to be (should) called in _setup_template_handler


Question:

How to register the handler that is in the TemplateHandlers not in ExtensionHandler


Hopefully this makes the issue clearer?

@derks
Copy link
Member

derks commented Mar 11, 2025

@TomFreudenberg I admit that I do not follow perfectly. The _setup_extension_handler() method is only calling _setup() on the app.ext (extension handler). However, there might have been some issues here due to a bug I found in Jinja2OutputHandler.

The Jinja2OutputHandler relies on the Jinja2TemplateHandler, so you will want to sub-class/implement both. I've done an example here.

Also note, that I implemented the add_template_dir in a framework hook so that it is run at the right time. The following are long/noisy examples (appologies) but I believe this will help accomplish what you are looking for.

myapp/ext/my_jinja2.py

from cement.core.output import OutputHandler
from cement.ext.ext_jinja2 import Jinja2OutputHandler, Jinja2TemplateHandler

### @derks > use a framework hook to run code at the desired runtime
def add_template_dirs(app):
    d = app.config.get('my.jinja2', 'template_dirs')
    for p in d:
        app.add_template_dir(p)


class MyJinja2OutputHandler(Jinja2OutputHandler):

    class Meta(Jinja2OutputHandler.Meta):
        label = 'my_jinja2'
        config_section = 'my.jinja2'

    def _setup(self, app) -> None:
        ### @derks > There is a bug in Jinja2OutputHandler
        ### Super OutputHandler here instead of MyJinja2OutputHandler
        super(OutputHandler, self)._setup(app)
        self.templater = self.app.handler.resolve('template', self._meta.label, setup=True)  # type: ignore

class MyJinja2TemplateHandler(Jinja2TemplateHandler):

    class Meta(Jinja2TemplateHandler.Meta):
        """Handler meta-data."""

        label = 'my_jinja2'

        #: Id for config
        config_section = 'my.jinja2'

        #: Configuration default values
        config_defaults = {
            'template_dirs': [],
        }

    def __init__(self, *args, **kw):
        super(MyJinja2TemplateHandler, self).__init__(*args, **kw)

    def _setup(self, app):
        super(MyJinja2TemplateHandler, self)._setup(app)


def load(app):
    app.handler.register(MyJinja2OutputHandler)
    app.handler.register(MyJinja2TemplateHandler)
    app.hook.register('pre_run', add_template_dirs)

main.py

from cement import App, TestApp, init_defaults
from cement.core.exc import CaughtSignal
from .core.exc import MyAppError
from .controllers.base import Base
from .ext.my_jinja2 import Jinja2TemplateHandler

# configuration defaults
CONFIG = init_defaults('myapp')
CONFIG['myapp']['foo'] = 'bar'

class MyApp(App):
    class Meta:
        label = 'myapp'

        # configuration defaults
        config_defaults = CONFIG

        # call sys.exit() on close
        exit_on_close = True

        # load additional framework extensions
        extensions = [
            'yaml',
            'colorlog',
            'myapp.ext.my_jinja2'
        ]

        # configuration handler
        config_handler = 'yaml'

        # configuration file suffix
        config_file_suffix = '.yml'

        # set the log handler
        log_handler = 'colorlog'

        # set the output handler
        output_handler = 'my_jinja2'

        # set template Handler
        template_handler = 'my_jinja2'

        # register handlers
        handlers = [
            Base
        ]


class MyAppTest(TestApp,MyApp):
    """A sub-class of MyApp that is better suited for testing."""

    class Meta:
        label = 'myapp'


def main():
    with MyApp() as app:
        try:
            app.run()
            # print(app.config.get_dict())
            print(f"APP TEMPLATE DIRS: {app._meta.template_dirs}")
        except AssertionError as e:
            print('AssertionError > %s' % e.args[0])
            app.exit_code = 1

            if app.debug is True:
                import traceback
                traceback.print_exc()

        except MyAppError as e:
            print('MyAppError > %s' % e.args[0])
            app.exit_code = 1

            if app.debug is True:
                import traceback
                traceback.print_exc()

        except CaughtSignal as e:
            # Default Cement signals are SIGINT and SIGTERM, exit 0 (non-error)
            print('\n%s' % e)
            app.exit_code = 0


if __name__ == '__main__':
    main()

Run (debug):

|> cement-py313 <| # myapp --debug 2>&1 | grep -i jinja2
...
2025-03-11 16:20:27,492 (DEBUG) cement.core.extension : loading the 'myapp.ext.my_jinja2' framework extension
2025-03-11 16:20:27,492 (DEBUG) cement.core.handler : registering handler '<class 'myapp.ext.my_jinja2.MyJinja2OutputHandler'>' into handlers['output']['my_jinja2']
2025-03-11 16:20:27,492 (DEBUG) cement.core.handler : registering handler '<class 'myapp.ext.my_jinja2.MyJinja2TemplateHandler'>' into handlers['template']['my_jinja2']
2025-03-11 16:20:27,492 (DEBUG) cement.core.hook : registering hook 'add_template_dirs' from myapp.ext.my_jinja2 into hooks['pre_run']
2025-03-11 16:20:27,492 (DEBUG) cement.core.handler : merging config defaults from '<myapp.ext.my_jinja2.MyJinja2TemplateHandler object at 0xffffae74a0d0>' into section 'my.jinja2'
2025-03-11 16:20:27,493 (DEBUG) cement.core.handler : merging config defaults from '<myapp.ext.my_jinja2.MyJinja2TemplateHandler object at 0xffffae74a5d0>' into section 'my.jinja2'
2025-03-11 16:20:27,493 (DEBUG) cement.core.handler : merging config defaults from '<myapp.ext.my_jinja2.MyJinja2TemplateHandler object at 0xffffae790050>' into section 'my.jinja2'
2025-03-11 16:20:27,493 (DEBUG) cement.core.hook : running hook 'pre_run' (<function add_template_dirs at 0xffffaefce160>) from myapp.ext.my_jinja2
APP TEMPLATE DIRS: ['/root/.myapp/templates', '/root/.config/myapp/templates', '/usr/lib/myapp/templates', '/path/to/my.jinja2/template/dir1']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants