Edgewall Software
Modify

Opened 9 months ago

Closed 8 months ago

#13745 closed defect (fixed)

Babel issue KeyError: 'dgettext'

Reported by: clemens Owned by: Jun Omae
Priority: normal Milestone: 1.6.1
Component: i18n Version: 1.6
Severity: normal Keywords:
Cc: c.feige@… Branch:
Release Notes:
  • Fixed KeyError raised when IRequestHandler.process_request returns domain parameter in the metadata.
  • Made tagn_, dtgettext and dtngettext available in Jinja2 templates.
API Changes:
Internal Changes:

Description

There seems to be a problem with babel with the CcSelectorPlugin. This plugin produces an error message if Babel is not installed. But since this root cause seems to be with the TRAC core, I am submitting the ticket here (and not at the Track-Hacks site).

Plugin error when Babel is not installed

I have been using the CcSelectorPlugin for many years. Now I re-installed my TRAC server (now with TRAC 1.6). Intentionally I decided to install TRAC without Babel because we do not want multi-lingual support.

After installing CcSelectorPlugin (Version 0.3, TrackHacks Revision 18597, from Nov. 2023, installation via PIP) it presents the following error when I click the "CC Selection" button:

Trac detected an internal error:
KeyError: 'dgettext'

The Python Traceback points to site-packages/trac/util/translation.py.

Python Traceback
File "/data/trac/python/lib/python3.11/site-packages/trac/web/main.py", line 609, in dispatch_request dispatcher.dispatch(req)
File "/data/trac/python/lib/python3.11/site-packages/trac/web/main.py", line 301, in dispatch raise e
File "/data/trac/python/lib/python3.11/site-packages/trac/web/main.py", line 265, in dispatch output = chrome.render_template(req, template, data, metadata)
File "/data/trac/python/lib/python3.11/site-packages/trac/web/chrome.py", line 1381, in render_template template, data = self.prepare_template(req, filename, data, text, 
File "/data/trac/python/lib/python3.11/site-packages/trac/web/chrome.py", line 1487, in prepare_template domain_functions = translation.domain_functions(domain, symbols)
File "/data/trac/python/lib/python3.11/site-packages/trac/util/translation.py", line 92, in domain_functions return [_functions[s] for s in symbols]
File "/data/trac/python/lib/python3.11/site-packages/trac/util/translation.py", line 92, in <listcomp> return [_functions[s] for s in symbols] 

Discussion

Of course the solution is to install Babel, which will solve the problem.

Nevertheless IMHO either a plug-in should clearly state its dependencies during installation (or during run-time or in the documentation). Best of course would be would be, if the plug-in could run without Babel. After all, Babel is just optional for TRAC.

Although I am reporting an issue with a plugin this is supposed to be an issue with the TRAC core. This issue was discussed on the TRAC mailing list and I was told to open a ticket here.

Attachments (0)

Change History (11)

comment:1 by Clemens <c.feige@…>, 9 months ago

I want to contribute some details about my TRAC where I found this issue:

  • Trac 1.6 running on Linux
  • Babel 2.14.0
  • Jinja2 3.1.3
  • Python 3.11.2

comment:2 by Clemens <c.feige@…>, 9 months ago

This is the answer I received from Jun Omae on the mailing list. He already made some experiments and statements which I am quoting here:

QUOTE:

That is an issue of Trac core since 1.4.x. It is reproduced with Trac 1.4.4 and 1.6 without Babel. Please report it to trac.edgewall.org/newticket.

TRAC 1.6:

$ python3.11 -m venv /dev/shm/trac-1.6
$ /dev/shm/trac-1.6/bin/pip install -q Trac~=1.6.0
$ /dev/shm/trac-1.6/bin/python
Python 3.11.8 (main, Feb 25 2024, 16:41:26) [GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

>>> import babel
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'babel'
>>> import trac
>>> trac.__version__
'1.6'
>>> from trac.test import EnvironmentStub, MockRequest
>>> from trac.web.chrome import Chrome
>>> env = EnvironmentStub()
>>> req = MockRequest(env)
>>> chrome = Chrome(env)
>>> chrome.prepare_template(req, 'layout.html', {}, domain='messages')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/trac/web/chrome.py", line 1487, in prepare_template
    domain_functions = translation.domain_functions(domain, symbols)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/trac/util/translation.py", line 92, in domain_functions
    return [_functions[s] for s in symbols]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/trac/util/translation.py", line 92, in <listcomp>
    return [_functions[s] for s in symbols]
            ~~~~~~~~~~^^^
KeyError: 'dgettext'
>>>

TRAC 1.4:

$ virtualenv -p /usr/bin/python2.7 /dev/shm/trac-1.4
$ /dev/shm/trac-1.4/bin/pip install -q Trac~=1.4.0
$ /dev/shm/trac-1.4/bin/python
Python 2.7.18 (default, Jul  1 2022, 12:27:04)
[GCC 9.4.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import babel
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named babel
>>> import trac
>>> trac.__version__
'1.4.4'
>>> from trac.test import EnvironmentStub, MockRequest
>>> from trac.web.chrome import Chrome
>>> env = EnvironmentStub()
>>> req = MockRequest(env)
>>> chrome = Chrome(env)
>>> chrome.prepare_template(req, 'layout.html', {}, domain='messages')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/dev/shm/trac-1.4/lib/python2.7/site-packages/trac/web/chrome.py", line 1614, in prepare_template
    domain_functions = translation.domain_functions(domain, symbols)
  File "/dev/shm/trac-1.4/lib/python2.7/site-packages/trac/util/translation.py", line 92, in domain_functions
    return [_functions[s] for s in symbols]
KeyError: 'dngettext'
>>>

comment:3 by Jun Omae, 9 months ago

This issue can be reproduced with/without Babel.

>>> import trac, babel
>>> (trac.__version__, babel.__version__)
('1.6', '2.14.0')
>>> from trac.env import Environment
>>> from trac.test import MockRequest
>>> from trac.web.chrome import Chrome
>>> from trac.util import read_file
>>>
>>> env = Environment('/dev/shm/tracenv-1.6')
>>> req = MockRequest(env)
>>> chrome = Chrome(env)
>>> print(read_file('/dev/shm/tracenv-1.6/templates/test.html'))
${dgettext("messages", "Your changes have been saved.")}

>>> t = chrome.render_template(req, 'test.html', {'data': 42}, {'domain': 'messages', 'iterable': False})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/trac/web/chrome.py", line 1411, in render_template
    return self.generate_template_stream(template, data, text,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/trac/web/chrome.py", line 1533, in generate_template_stream
    return b''.join(generate())
           ^^^^^^^^^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/trac/web/chrome.py", line 1531, in generate
    for chunk in stream:
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/jinja2/environment.py", line 1662, in __next__
    return self._next()  # type: ignore
           ^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/jinja2/environment.py", line 1639, in _buffered_generator
    c = next(self._gen)
        ^^^^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/jinja2/environment.py", line 1354, in generate
    yield self.environment.handle_exception()
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/jinja2/environment.py", line 936, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "/dev/shm/tracenv-1.6/templates/test.html", line 1, in top-level template code
    ${dgettext("messages", "Your changes have been saved.")}
    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/dev/shm/trac-1.6/lib/python3.11/site-packages/trac/util/translation.py", line 295, in <lambda>
    return lambda *args, **kw: _functions[symbol](domain, *args, **kw)
                               ~~~~~~~~~~^^^^^^^^
KeyError: 'dgettext'
>>>

comment:4 by Jun Omae, 9 months ago

Component: generalrendering
Milestone: 1.6.1
Owner: set to Jun Omae
Status: newassigned

comment:5 by Dirk Stöcker, 9 months ago

The current version of CCSelectorPlugin from trunk has nowhere "dgettext" in the code. Which means you're using an old version.

Correct plugin for ≥ 1.6 can be found here: https://trac-hacks.org/svn/ccselectorplugin/trunk/

I don't think there is anything wrong in core.

comment:6 by Jun Omae, 9 months ago

Any plugins don't have the root cause. It occurs when process_request returns domain in metadata parameter. Chrome class injects domain functions (_, gettext, dgettext, …) to the template instance on rendering process, however, KeyError is raised.

The issue is absolutely in Trac core.

>>> import babel
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'babel'
>>> from trac.env import Environment
>>> from trac.test import EnvironmentStub, MockRequest
>>> from trac.web.api import IRequestHandler
>>> from trac.core import Component, implements
>>> from trac.web.main import RequestDispatcher
>>>
>>> class Foo(Component):
...   implements(IRequestHandler)
...   def match_request(self, req):
...     return req.path_info == '/test'
...   def process_request(self, req):
...     return 'layout.html', {'data': 42}, {'domain': 'messages'}
...
>>> env = EnvironmentStub()
>>> req = MockRequest(env, path_info='/test')
>>> dispatcher = RequestDispatcher(env)
>>> dispatcher.dispatch(req)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jun66j5/src/tracdev/git/trac/web/main.py", line 301, in dispatch
    raise e
  File "/home/jun66j5/src/tracdev/git/trac/web/main.py", line 265, in dispatch
    output = chrome.render_template(req, template, data, metadata)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jun66j5/src/tracdev/git/trac/web/chrome.py", line 1381, in render_template
    template, data = self.prepare_template(req, filename, data, text,
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jun66j5/src/tracdev/git/trac/web/chrome.py", line 1487, in prepare_template
    domain_functions = translation.domain_functions(domain, symbols)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jun66j5/src/tracdev/git/trac/util/translation.py", line 92, in domain_functions
    return [_functions[s] for s in symbols]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/jun66j5/src/tracdev/git/trac/util/translation.py", line 92, in <listcomp>
    return [_functions[s] for s in symbols]
            ~~~~~~~~~~^^^
KeyError: 'dgettext'
>>>

in reply to:  5 ; comment:7 by Clemens <c.feige@…>, 9 months ago

Replying to Dirk Stöcker:

The current version of CCSelectorPlugin from trunk has nowhere "dgettext" in the code. Which means you're using an old version.

Correct plugin for ≥ 1.6 can be found here: https://trac-hacks.org/svn/ccselectorplugin/trunk/

I don't think there is anything wrong in core.

I am quite sure that I am using the most recent version of the CCSelectorPlugin (Version 0.3, TrackHacks Revision 18597, from Nov. 2023) because I just installed it myself few days ago with this command:

pip install svn+https://trac-hacks.org/svn/ccselectorplugin/trunk/

Yes, you are right, the text dgettextdoes not appear in the plug-in code. This was something I checked when I came across this error, too. I am not sure if this tells us where this root problem is, in the plugin or in the core.

Seems that Jun has found something…

comment:8 by Jun Omae, 8 months ago

Release Notes: modified (diff)

Proposed changes in [bd056f646/jomae.git].

in reply to:  7 comment:9 by Jun Omae, 8 months ago

Replying to Clemens <c.feige@…>:

Yes, you are right, the text dgettextdoes not appear in the plug-in code. This was something I checked when I came across this error, too. I am not sure if this tells us where this root problem is, in the plugin or in the core.

That is a defect in Trac core. Work around for the plugin is avoid process_request returning {'domain': 'cc_selector'} as metadata like the following:

  • cc_selector/cc_selector.py

     
    7777            'cc_developers': cc_developers,
    7878            'show_fullname': self.show_fullname
    7979        }
    80         return 'cc_selector_jinja.html', data, {'domain': 'cc_selector'}
     80        return 'cc_selector_jinja.html', data
  • cc_selector/templates/cc_selector_jinja.html

     
    11# import "macros.html" as jmacros with context
     2# set _ = partial(dgettext, 'cc_selector')
    23<!DOCTYPE html>
    34<html>
    45  <head>

comment:10 by Jun Omae, 8 months ago

Added [ac30ba8be/jomae.git] to the proposed changes. The tag_ function is available in Jinja2 template but tagn_, dtgettext and dtngettext are unavailable. The changes allow the latter functions.

comment:11 by Jun Omae, 8 months ago

Component: renderingi18n
Release Notes: modified (diff)
Resolution: fixed
Status: assignedclosed

Fixed in [17794] and merged in [17795].

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Jun Omae.
The resolution will be deleted. Next status will be 'reopened'.
to The owner will be changed from Jun Omae to the specified user.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.