Re: [Freeipa-devel] [PATCH] jderose 052 Finish deferred translation mechanism

2010-03-15 Thread Jason Gerard DeRose
On Fri, 2010-03-12 at 11:31 -0500, John Dennis wrote:
> On 03/08/2010 11:25 PM, Jason Gerard DeRose wrote:
> > This patch finishes the the LazyText functionality in the ipalib.text
> > module.  This patch includes extensive docstrings in text.py that should
> > hopefully explain everything pretty well.  There's also now pretty darn
> > complete test coverage.  Still to do:
> >
> >1. Have Backend.session extract the locale and set
> >   context.languages... I have an rpcserver cleanup patch I've been
> >   working on which will include this change.
> >
> >2. Remove deprecated gettext stuff in ipalib.request... this is a
> >   small change, but I left it out of this patch so it's easier to
> >   review
> >
> > I'll have these next two patches later this week.
> 
> I've tested this and it works for me and seems pretty clean, a good 
> patch. Thank you Jason. However I do have one thing which I'd like to 
> see cleaned up, it's a few naming issues (see below).

Well, naming issues aside, is this an ack?  Do you mind if I push this
patch and then possibly push a tune-up patch?

> In a moment I'm going to follow up with a patch that extends 
> tests/test_ipalib/test_text.py to utilize the test language you asked 
> for and is currently in install/po. That test is implemented and working 
> so look for the patch in a moment.
> 
> Naming Issues:
> 
> The thread local object can be assigned attributes directly and it's 
> attributes can be referenced directly. Using context.__dict__ seems odd 

Although it isn't usually standard to use an instance dictionary like
this, the Python threading.local documentation specifically endorses it.
After reading the docstring in /usr/lib64/python2.6/_threading_local.py,
my impression is that threading.local is indented to be used both as an
instance to store thread-local attributes, and as a dict to store
thread-local items (regardless of whether the keys are valid attribute
names).

John, could you take a look at this documentation and let me know if you
concur?

> and unnecessary to store the language keys. I presume you're doing that 
> because you can't have a tuple as an attribute name on the context. 
> Directly accessing the __dict__ of an object feels like something we 
> should avoid if possible. Also we're stuffing unrelated items in 
> context.__dict__, for example the Connection and language keys are being 
> stored together. Wouldn't be cleaner to keep the language keys in their 
> own "name space" and to use constructs like this:
> 
> context = threading.local()
> context.connection = Connection()
> context.language_keys = {}
> context.language_keys[key] = translation
> if key in context.language_keys

As you have it above, context.language_keys only exists in the current
thread.  So each time we would have to check if the language_keys dict
has been created in the current thread, then check if the key is
present.

If you want these separated, I personally think a second threading.local
instance should be used, something like:

language_keys = threading.local()

I actually had them separated like this initially but decided to combine
them so there is only one threading.local instance we need to clear()
after processing a request.

Also, though it seems messy to combine all of these in the context, the
name-spaces don't overlap... a tuple will never equal an attribute name
(str), so the translations can't conflict with any attributes we store
on the context.

> rather than
> 
> context.__dict__[key] = translation
> if key in context.__dict__
> 
> This also means when you clear the context you don't have to iterate 
> over the members of context.__dict__ and special case the values as is 
> currently being done with:
> 
>  for (name, value) in context.__dict__.items():
>  if isinstance(value, Connection):
>  value.disconnect()
> 
> Wouldn't this be cleaner as:
> 
> if context.connection:
>  context.connection.disconnect()

We can have multiple connections, which is why we do this iteration with
type checking.  An LDAP connection is always created, but other
connections might also be created.  Currently the only place we are
doing this is for a connection to the certificate server, but we should
allow plugins to create additional connections, and have them explicitly
disconnected by request.destroy_context(). 

> Keeping the language keys separately would also allow us to clear the 
> language keys independently of anything else in the context without 
> having to worry about what else we might clobber in the context.

I have no problem using a separate threading.local() instance for the
translations if you feel that is the better approach.  Small change.

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


Re: [Freeipa-devel] [PATCH] jderose 052 Finish deferred translation mechanism

2010-03-12 Thread John Dennis

On 03/08/2010 11:25 PM, Jason Gerard DeRose wrote:

This patch finishes the the LazyText functionality in the ipalib.text
module.  This patch includes extensive docstrings in text.py that should
hopefully explain everything pretty well.  There's also now pretty darn
complete test coverage.  Still to do:

   1. Have Backend.session extract the locale and set
  context.languages... I have an rpcserver cleanup patch I've been
  working on which will include this change.

   2. Remove deprecated gettext stuff in ipalib.request... this is a
  small change, but I left it out of this patch so it's easier to
  review

I'll have these next two patches later this week.


I've tested this and it works for me and seems pretty clean, a good 
patch. Thank you Jason. However I do have one thing which I'd like to 
see cleaned up, it's a few naming issues (see below).


In a moment I'm going to follow up with a patch that extends 
tests/test_ipalib/test_text.py to utilize the test language you asked 
for and is currently in install/po. That test is implemented and working 
so look for the patch in a moment.


Naming Issues:

The thread local object can be assigned attributes directly and it's 
attributes can be referenced directly. Using context.__dict__ seems odd 
and unnecessary to store the language keys. I presume you're doing that 
because you can't have a tuple as an attribute name on the context. 
Directly accessing the __dict__ of an object feels like something we 
should avoid if possible. Also we're stuffing unrelated items in 
context.__dict__, for example the Connection and language keys are being 
stored together. Wouldn't be cleaner to keep the language keys in their 
own "name space" and to use constructs like this:


context = threading.local()
context.connection = Connection()
context.language_keys = {}
context.language_keys[key] = translation
if key in context.language_keys

rather than

context.__dict__[key] = translation
if key in context.__dict__

This also means when you clear the context you don't have to iterate 
over the members of context.__dict__ and special case the values as is 
currently being done with:


for (name, value) in context.__dict__.items():
if isinstance(value, Connection):
value.disconnect()

Wouldn't this be cleaner as:

if context.connection:
context.connection.disconnect()

Keeping the language keys separately would also allow us to clear the 
language keys independently of anything else in the context without 
having to worry about what else we might clobber in the context.


--
John Dennis 

Looking to carve out IT costs?
www.redhat.com/carveoutcosts/

___
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel


[Freeipa-devel] [PATCH] jderose 052 Finish deferred translation mechanism

2010-03-08 Thread Jason Gerard DeRose
This patch finishes the the LazyText functionality in the ipalib.text
module.  This patch includes extensive docstrings in text.py that should
hopefully explain everything pretty well.  There's also now pretty darn
complete test coverage.  Still to do:

  1. Have Backend.session extract the locale and set
 context.languages... I have an rpcserver cleanup patch I've been
 working on which will include this change.

  2. Remove deprecated gettext stuff in ipalib.request... this is a
 small change, but I left it out of this patch so it's easier to
 review

I'll have these next two patches later this week.


>From 1b86cff2e402393c0ea8fdb472bb9cc665d2cda7 Mon Sep 17 00:00:00 2001
From: Jason Gerard DeRose 
Date: Mon, 8 Mar 2010 20:42:26 -0700
Subject: [PATCH] Finish deferred translation mechanism

---
 ipalib/__init__.py |2 +-
 ipalib/parameters.py   |6 +-
 ipalib/request.py  |5 +-
 ipalib/text.py |  447 ++--
 tests/test_ipalib/test_text.py |  135 +++-
 tests/util.py  |6 +-
 6 files changed, 560 insertions(+), 41 deletions(-)

diff --git a/ipalib/__init__.py b/ipalib/__init__.py
index 51b63c9..6545bf7 100644
--- a/ipalib/__init__.py
+++ b/ipalib/__init__.py
@@ -881,7 +881,7 @@ from crud import Create, Retrieve, Update, Delete, Search
 from parameters import DefaultFrom, Bool, Flag, Int, Float, Bytes, Str, Password,List
 from parameters import BytesEnum, StrEnum, AccessTime, File
 from errors import SkipPluginModule
-from text import _, gettext, ngettext
+from text import _, ngettext, GettextFactory, NGettextFactory
 
 # We can't import the python uuid since it includes ctypes which makes
 # httpd throw up when run in in mod_python due to SELinux issues
diff --git a/ipalib/parameters.py b/ipalib/parameters.py
index a598690..606a574 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -73,14 +73,14 @@ web-UI.  The *label* should start with an initial capital.  For example:
 ... label=_('Last name'),
 ... )
 >>> sn.label
-Gettext('Last name')
+Gettext('Last name', domain='ipa', localedir=None)
 
 The *doc* is a longer description of the parameter.  It's used on the CLI when
 displaying the help information for a command, and as extra instruction for a
 form input on the web-UI.  By default the *doc* is the same as the *label*:
 
 >>> sn.doc
-Gettext('Last name')
+Gettext('Last name', domain='ipa', localedir=None)
 
 But you can override this with the *doc* kwarg.  Like the *label*, the *doc*
 should also start with an initial capital and should not end with any
@@ -92,7 +92,7 @@ punctuation.  For example:
 ... doc=_("The user's last name"),
 ... )
 >>> sn.doc
-Gettext("The user's last name")
+Gettext("The user's last name", domain='ipa', localedir=None)
 
 Demonstration aside, you should always provide at least the *label* so the
 various UIs are translatable.  Only provide the *doc* if the parameter needs
diff --git a/ipalib/request.py b/ipalib/request.py
index f21ac03..86b6433 100644
--- a/ipalib/request.py
+++ b/ipalib/request.py
@@ -52,10 +52,11 @@ def destroy_context():
 """
 Delete all attributes on thread-local `request.context`.
 """
-# need to use .items(), 'cos value.disconnect modifies the dict
-for (name, value) in context.__dict__.items():
+# need to use .values(), 'cos value.disconnect modifies the dict
+for value in context.__dict__.values():
 if isinstance(value, Connection):
 value.disconnect()
+context.__dict__.clear()
 
 
 def ugettext(message):
diff --git a/ipalib/text.py b/ipalib/text.py
index cabca43..96770ad 100644
--- a/ipalib/text.py
+++ b/ipalib/text.py
@@ -18,73 +18,480 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 
 """
-Thread-local lazy gettext service.
+Defers gettext translation till request time.
 
-TODO: This aren't hooked up into gettext yet, they currently just provide
-placeholders for the rest of the code.
+IPA presents some tricky gettext challenges.  On the one hand, most translatable
+message are defined as class attributes on the plugins, which means these get
+evaluated at module-load time.  But on the other hand, each request to the
+server can be in a different locale, so the actual translation must not occur
+till request time.
+
+The `text` module provides a mechanism for for deferred gettext translation.  It
+was designed to:
+
+1. Allow translatable strings to be marked with the usual ``_()`` and
+   ``ngettext()`` functions so that standard tools like xgettext can still
+   be used
+
+2. Allow programmers to mark strings in a natural way without burdening them
+   with details of the deferred translation mechanism
+
+A typical plugin will use the deferred translation like this:
+
+>>> from ipalib import Command, _, ngettext
+>>> class my_plugin(Command):
+... my_string = _('Hello, %(name)s.')
+...