Mercurial > genshi > genshi-test
comparison genshi/template/eval.py @ 917:bcaa91c42b97 experimental-py3k
add support for python 3 to genshi.template expression evaluator:
* add support for python 3 AST:
* AST for raise has changed in Python 3.
* Python 3 adds AST nodes for individual arguments and Bytes.
* use genshi.compat functions for dealing with code objects.
* do not coerce byte strings to unicode in Python 3 ASTTransformer.
* replace doctests that reply on exception names with uglier but more compatible try:.. except:.. doctest
* handle filename preferences of Python 2 and 3 (2 prefers bytes, 3 prefers unicode).
* ifilter is gone from itertools in Python 3 so use repeat for tests instead.
author | hodgestar |
---|---|
date | Sun, 24 Oct 2010 22:39:08 +0000 |
parents | 85e4678337cf |
children |
comparison
equal
deleted
inserted
replaced
916:872726bac135 | 917:bcaa91c42b97 |
---|---|
21 from genshi.core import Markup | 21 from genshi.core import Markup |
22 from genshi.template.astutil import ASTTransformer, ASTCodeGenerator, \ | 22 from genshi.template.astutil import ASTTransformer, ASTCodeGenerator, \ |
23 _ast, parse | 23 _ast, parse |
24 from genshi.template.base import TemplateRuntimeError | 24 from genshi.template.base import TemplateRuntimeError |
25 from genshi.util import flatten | 25 from genshi.util import flatten |
26 | |
27 from genshi.compat import get_code_params, build_code_chunk, IS_PYTHON2 | |
26 | 28 |
27 __all__ = ['Code', 'Expression', 'Suite', 'LenientLookup', 'StrictLookup', | 29 __all__ = ['Code', 'Expression', 'Suite', 'LenientLookup', 'StrictLookup', |
28 'Undefined', 'UndefinedError'] | 30 'Undefined', 'UndefinedError'] |
29 __docformat__ = 'restructuredtext en' | 31 __docformat__ = 'restructuredtext en' |
30 | 32 |
96 self._globals = lookup.globals | 98 self._globals = lookup.globals |
97 | 99 |
98 def __getstate__(self): | 100 def __getstate__(self): |
99 state = {'source': self.source, 'ast': self.ast, | 101 state = {'source': self.source, 'ast': self.ast, |
100 'lookup': self._globals.im_self} | 102 'lookup': self._globals.im_self} |
101 c = self.code | 103 state['code'] = get_code_params(self.code) |
102 state['code'] = (c.co_nlocals, c.co_stacksize, c.co_flags, c.co_code, | |
103 c.co_consts, c.co_names, c.co_varnames, c.co_filename, | |
104 c.co_name, c.co_firstlineno, c.co_lnotab, (), ()) | |
105 return state | 104 return state |
106 | 105 |
107 def __setstate__(self, state): | 106 def __setstate__(self, state): |
108 self.source = state['source'] | 107 self.source = state['source'] |
109 self.ast = state['ast'] | 108 self.ast = state['ast'] |
234 | 233 |
235 However, calling an undefined variable, or trying to access an attribute | 234 However, calling an undefined variable, or trying to access an attribute |
236 of that variable, will raise an exception that includes the name used to | 235 of that variable, will raise an exception that includes the name used to |
237 reference that undefined variable. | 236 reference that undefined variable. |
238 | 237 |
239 >>> foo('bar') | 238 >>> try: |
240 Traceback (most recent call last): | 239 ... foo('bar') |
241 ... | 240 ... except UndefinedError, e: |
242 UndefinedError: "foo" not defined | 241 ... print e.msg |
243 | 242 "foo" not defined |
244 >>> foo.bar | 243 |
245 Traceback (most recent call last): | 244 >>> try: |
246 ... | 245 ... foo.bar |
247 UndefinedError: "foo" not defined | 246 ... except UndefinedError, e: |
247 ... print e.msg | |
248 "foo" not defined | |
248 | 249 |
249 :see: `LenientLookup` | 250 :see: `LenientLookup` |
250 """ | 251 """ |
251 __slots__ = ['_name', '_owner'] | 252 __slots__ = ['_name', '_owner'] |
252 | 253 |
386 | 387 |
387 Referencing an undefined variable using this lookup style will immediately | 388 Referencing an undefined variable using this lookup style will immediately |
388 raise an ``UndefinedError``: | 389 raise an ``UndefinedError``: |
389 | 390 |
390 >>> expr = Expression('nothing', lookup='strict') | 391 >>> expr = Expression('nothing', lookup='strict') |
391 >>> expr.evaluate({}) | 392 >>> try: |
392 Traceback (most recent call last): | 393 ... expr.evaluate({}) |
393 ... | 394 ... except UndefinedError, e: |
394 UndefinedError: "nothing" not defined | 395 ... print e.msg |
396 "nothing" not defined | |
395 | 397 |
396 The same happens when a non-existing attribute or item is accessed on an | 398 The same happens when a non-existing attribute or item is accessed on an |
397 existing object: | 399 existing object: |
398 | 400 |
399 >>> expr = Expression('something.nil', lookup='strict') | 401 >>> expr = Expression('something.nil', lookup='strict') |
400 >>> expr.evaluate({'something': dict()}) | 402 >>> try: |
401 Traceback (most recent call last): | 403 ... expr.evaluate({'something': dict()}) |
402 ... | 404 ... except UndefinedError, e: |
403 UndefinedError: {} has no member named "nil" | 405 ... print e.msg |
406 {} has no member named "nil" | |
404 """ | 407 """ |
405 | 408 |
406 @classmethod | 409 @classmethod |
407 def undefined(cls, key, owner=UNDEFINED): | 410 def undefined(cls, key, owner=UNDEFINED): |
408 """Raise an ``UndefinedError`` immediately.""" | 411 """Raise an ``UndefinedError`` immediately.""" |
419 rest = dedent('\n'.join(lines[1:])).rstrip() | 422 rest = dedent('\n'.join(lines[1:])).rstrip() |
420 if first.rstrip().endswith(':') and not rest[0].isspace(): | 423 if first.rstrip().endswith(':') and not rest[0].isspace(): |
421 rest = '\n'.join([' %s' % line for line in rest.splitlines()]) | 424 rest = '\n'.join([' %s' % line for line in rest.splitlines()]) |
422 source = '\n'.join([first, rest]) | 425 source = '\n'.join([first, rest]) |
423 if isinstance(source, unicode): | 426 if isinstance(source, unicode): |
424 source = '\xef\xbb\xbf' + source.encode('utf-8') | 427 source = (u'\ufeff' + source).encode('utf-8') |
425 return parse(source, mode) | 428 return parse(source, mode) |
426 | 429 |
427 | 430 |
428 def _compile(node, source=None, mode='eval', filename=None, lineno=-1, | 431 def _compile(node, source=None, mode='eval', filename=None, lineno=-1, |
429 xform=None): | 432 xform=None): |
430 if isinstance(filename, unicode): | 433 if not filename: |
431 # unicode file names not allowed for code objects | |
432 filename = filename.encode('utf-8', 'replace') | |
433 elif not filename: | |
434 filename = '<string>' | 434 filename = '<string>' |
435 if IS_PYTHON2: | |
436 # Python 2 requires non-unicode filenames | |
437 if isinstance(filename, unicode): | |
438 filename = filename.encode('utf-8', 'replace') | |
439 else: | |
440 # Python 3 requires unicode filenames | |
441 if not isinstance(filename, unicode): | |
442 filename = filename.decode('utf-8', 'replace') | |
435 if lineno <= 0: | 443 if lineno <= 0: |
436 lineno = 1 | 444 lineno = 1 |
437 | 445 |
438 if xform is None: | 446 if xform is None: |
439 xform = { | 447 xform = { |
456 code = compile(new_source, filename, mode) | 464 code = compile(new_source, filename, mode) |
457 | 465 |
458 try: | 466 try: |
459 # We'd like to just set co_firstlineno, but it's readonly. So we need | 467 # We'd like to just set co_firstlineno, but it's readonly. So we need |
460 # to clone the code object while adjusting the line number | 468 # to clone the code object while adjusting the line number |
461 return CodeType(0, code.co_nlocals, code.co_stacksize, | 469 return build_code_chunk(code, filename, name, lineno) |
462 code.co_flags | 0x0040, code.co_code, code.co_consts, | |
463 code.co_names, code.co_varnames, filename, name, | |
464 lineno, code.co_lnotab, (), ()) | |
465 except RuntimeError: | 470 except RuntimeError: |
466 return code | 471 return code |
467 | 472 |
468 | 473 |
469 def _new(class_, *args, **kwargs): | 474 def _new(class_, *args, **kwargs): |
491 self.locals = [CONSTANTS] | 496 self.locals = [CONSTANTS] |
492 | 497 |
493 def _extract_names(self, node): | 498 def _extract_names(self, node): |
494 names = set() | 499 names = set() |
495 def _process(node): | 500 def _process(node): |
501 if not IS_PYTHON2 and isinstance(node, _ast.arg): | |
502 names.add(node.arg) | |
496 if isinstance(node, _ast.Name): | 503 if isinstance(node, _ast.Name): |
497 names.add(node.id) | 504 names.add(node.id) |
498 elif isinstance(node, _ast.alias): | 505 elif isinstance(node, _ast.alias): |
499 names.add(node.asname or node.name) | 506 names.add(node.asname or node.name) |
500 elif isinstance(node, _ast.Tuple): | 507 elif isinstance(node, _ast.Tuple): |
511 for elt in node.names: | 518 for elt in node.names: |
512 _process(elt) | 519 _process(elt) |
513 return names | 520 return names |
514 | 521 |
515 def visit_Str(self, node): | 522 def visit_Str(self, node): |
516 if isinstance(node.s, str): | 523 if not isinstance(node.s, unicode): |
517 try: # If the string is ASCII, return a `str` object | 524 try: # If the string is ASCII, return a `str` object |
518 node.s.decode('ascii') | 525 node.s.decode('ascii') |
519 except ValueError: # Otherwise return a `unicode` object | 526 except ValueError: # Otherwise return a `unicode` object |
520 return _new(_ast.Str, node.s.decode('utf-8')) | 527 return _new(_ast.Str, node.s.decode('utf-8')) |
521 return node | 528 return node |