Getting clear error messages with a Python templating system?

2008-07-21 Thread Tom Machinski
Hi there,

I'm developing web applications in Python, so I use a templating system to
produce HTML.

We're dealing with heavy traffic here, so my original choice was the fast
and efficient Cheetah. The main problem is that Cheetah doesn't provide
clear error messages. Even in the best cases, you only get some indication
of the nature of the error ("TypeError: 'str' object is not callable") but
you can't see the code where the error occurs. You can't even know the
approximate region of the template code where the problem originates.

So I tried another templating system, the newer and equally efficient Mako.
Again, same problem. It should be noted that both Cheetah and Mako compile
themselves to an intermediary Python file. This Python file is what's
actually running, so that's where the error originates. This is very
efficient, but also severely hinders debugging (see below for example of
what a very simple error on a very short Mako template generates).

So my question is: is there any way to get clear error messages with a
Python templating system?

I welcome both suggestion on how to get those with Cheetah (or Mako), and
suggestions about alternative templating systems that provide better error
messages.

Thanks,

Tom

-

# foo.mako:
hello ${data}

>>> print mako.template.Template(filename="foo.mako").render(data="world")
hello world

>>> print
mako.template.Template(filename="foo.mako").render(dataerr="world")
Traceback (most recent call last):
  File "", line 1, in 
  File
"/usr/lib/python2.5/site-packages/Mako-0.1.10-py2.5.egg/mako/template.py",
line 114, in render
return runtime._render(self, self.callable_, args, data)
  File
"/usr/lib/python2.5/site-packages/Mako-0.1.10-py2.5.egg/mako/runtime.py",
line 287, in _render
_render_context(template, callable_, context, *args,
**_kwargs_for_callable(callable_, data))
  File
"/usr/lib/python2.5/site-packages/Mako-0.1.10-py2.5.egg/mako/runtime.py",
line 304, in _render_context
_exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
  File
"/usr/lib/python2.5/site-packages/Mako-0.1.10-py2.5.egg/mako/runtime.py",
line 337, in _exec_template
callable_(context, *args, **kwargs)
  File "foo_mako", line 19, in render_body
  File
"/usr/lib/python2.5/site-packages/Mako-0.1.10-py2.5.egg/mako/runtime.py",
line 91, in __str__
raise NameError("Undefined")
NameError: Undefined
--
http://mail.python.org/mailman/listinfo/python-list

Re: Python Written in C?

2008-07-21 Thread Tom Machinski
On Mon, Jul 21, 2008 at 3:53 PM, DaveM <[EMAIL PROTECTED]> wrote:

> On Mon, 21 Jul 2008 03:18:01 +0200, Michiel Overtoom <[EMAIL PROTECTED]>
> wrote:
>
>
> >Many major text/word processing programs (Emacs, vi, MS-Word) are also
> >written in C.
>
> I thought Emacs was written in Lisp.


Large parts of Emacs are indeed implemented in Emacs Lisp. There's are some
core functions implemented in C.

MS-Word, afaik, had very substantial parts written in Visual Basic.

Tom


> DaveM
> --
> http://mail.python.org/mailman/listinfo/python-list
>
--
http://mail.python.org/mailman/listinfo/python-list

Dangerous behavior of list(generator)

2009-12-12 Thread Tom Machinski
In most cases, `list(generator)` works as expected. Thus,
`list()` is generally equivalent to `[]`.

Here's a minimal case where this equivalence breaks, causing a serious
and hard-to-detect bug in a program:

  >>> def sit(): raise StopIteration()
  ...
  >>> [f() for f in (lambda:1, sit, lambda:2)]
  Traceback (most recent call last):
File "", line 1, in 
File "", line 1, in sit
  StopIteration
  >>> list(f() for f in (lambda:1, sit, lambda:2))
  [1]

I was bitten hard by this inconsistency when sit() was returning the
idiom `(foo for foo in bar if foo.is_baz()).next()`. The nonexistence
of a foo with is_baz() True in that query raises an exception as
designed, which expresses itself when I use the list comprehension
version of the code above; the generator version muffles the error and
silently introduces a subtle, confusing bug: `lambda:2` is never
reached, and a truncated list of 1 element (instead of 3) is
"successfully" generated..

Just wondered what you guys think,

 -- Tom
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Dangerous behavior of list(generator)

2009-12-30 Thread Tom Machinski
Thanks for the comment and discussion guys.

Bottom line, I'm going to have to remove this pattern from my code:

  foo = (foo for foo in foos if foo.bar).next()

I used to have that a lot in cases where not finding at least one
valid foo is an actual fatal error. But using StopIteration to signal
a fatal condition becomes a bug when interacting with list() as shown
in the original post.

It would be nice if there was a builtin for "get the first element in
a genexp, or raise an exception (which isn't StopIteration)", sort of
like:

  from itertools import islice

  def first_or_raise(genexp):
  L = list(islice(genexp, 1))
  if not L:
  raise RuntimeError('no elements found')
  return L[0]

I also think Jean-Paul's had a good point about how the problems in
the  list/genexp interaction could be addressed.

Thank you,

  -- Tom
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Dangerous behavior of list(generator)

2009-12-31 Thread Tom Machinski
On Wed, Dec 30, 2009 at 4:01 PM, Steven D'Aprano
 wrote:
> On Wed, 30 Dec 2009 15:18:11 -0800, Tom Machinski wrote:
>> Bottom line, I'm going to have to remove this pattern from my code:
>>
>>   foo = (foo for foo in foos if foo.bar).next()
>
> I don't see why. What's wrong with it? Unless you embed it in a call to
> list, or similar, it will explicitly raise StopIteration as expected.

Exactly; this seems innocuous, but if some caller of this code uses it
in a list() constructor, a very subtle and dangerous bug is introduced
- see OP. This is the entire point of this post.

In a large, non-trivial application, you simply cannot afford the
assumption that no caller will ever do that. Even if you have perfect
memory, some of your other developers or library users may not.

As for what's wrong with the "if not any" solution, Benjamin Kaplan's
post hits the nail on its head. This is a bioinformatics application,
so the iterable "foos" tends to be very large, so saving half the
runtime makes a big difference.

  -- Tom
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Dangerous behavior of list(generator)

2009-12-31 Thread Tom Machinski
On Thu, Dec 31, 2009 at 1:54 AM, Peter Otten <__pete...@web.de> wrote:
> Somewhat related in 2.6 there's the next() built-in which accepts a default
> value. You can provide a sentinel and test for that instead of using
> try...except:

Thanks. This can be useful in some of the simpler cases. As you surely
realize, to be perfectly safe, especially when the iterable can
contain any value (including your sentinel), we must use an
out-of-band return value, hence an exception is the only truly safe
solution.

  -- Tom
-- 
http://mail.python.org/mailman/listinfo/python-list


Re: Dangerous behavior of list(generator)

2009-12-31 Thread Tom Machinski
On Thu, Dec 31, 2009 at 12:18 PM, Stephen Hansen  wrote:
> Hmm? Just use a sentinel which /can't/ exist in the list: then its truly
> safe. If the list can contain all the usual sort of sentinels (False, None,
> 0, -1, whatever), then just make a unique one all your own.
> sentinel = object()
> if next(g(), sentinel) is sentinel:
>     ...
> Its impossible to get a false-positive then, as nothing g() can ever produce
> would ever be precisely "sentinel" (which would usually for me be some
> global const if I need to do such things in multiple places).
> --S

That's not a bad idea. Another nice feature is support for callable
"default" values; it would make several new things easier, including
raising an exception when you really want that (i.e. if not finding a
single element is truly exceptional).

 -- Tom
-- 
http://mail.python.org/mailman/listinfo/python-list