Ticket #113: patch
| File patch, 14.7 KB (added by darkness-keyword-genshi.e8bcba@…, 16 years ago) |
|---|
-
genshi/template/eval.py
=== modified file 'genshi/template/eval.py'
236 236 raise UndefinedError(self._name, self._owner) 237 237 __call__ = __getattr__ = __getitem__ = _die 238 238 239 class AccessWrapper (object): 240 def __init__(self, target): 241 # Being defensive about name collisions. Have to use setattr 242 # and a string to avoid name munging, since I don't want to 243 # duplicate it for __getattribute__ below. 244 object.__setattr__(self, "__target", target) 245 246 def __transparently__(self, action, exceptions, fallback, on_error=None): 247 __traceback_hide__ = True 248 if not callable(on_error): 249 def on_error(target, exc_info): 250 raise exc_info[0], exc_info[1], exc_info[2] 251 252 target = object.__getattribute__(self, "__target") 253 try: 254 return action(target) 255 except exceptions: 256 exc_info = sys.exc_info() 257 try: 258 try: 259 return fallback(target) 260 except: 261 return on_error(target, exc_info) 262 finally: 263 del exc_info 264 265 def __getattribute__(self, name, on_error=None): 266 __traceback_hide__ = True 267 if name == "__transparently__": 268 return object.__getattribute__(self, "__transparently__") 269 270 def action(target): 271 return object.__getattribute__(target, name) 272 def fallback(target): 273 return target[name] 274 return self.__transparently__(action, AttributeError, fallback, 275 on_error) 276 277 def __setattr__(self, name, value): 278 __traceback_hide__ = True 279 def action(target): 280 setattr(target, name, value) 281 def fallback(target): 282 target[name] = value 283 self.__transparently__(action, AttributeError, fallback) 284 285 def __delattr__(self, name): 286 __traceback_hide__ = True 287 def action(target): 288 delattr(target, name) 289 def fallback(target): 290 del target[name] 291 self.__transparently__(action, AttributeError, fallback) 292 293 def __getitem__(self, key, on_error=None): 294 __traceback_hide__ = True 295 def action(target): 296 return target[key] 297 def fallback(target): 298 return getattr(target, key) 299 return self.__transparently__(action, 300 (KeyError, IndexError, TypeError), 301 fallback, 302 on_error) 303 304 def __setitem__(self, key, value): 305 __traceback_hide__ = True 306 def action(target): 307 target[key] = value 308 def fallback(target): 309 setattr(target, key, value) 310 self.__transparently__(action, (IndexError, TypeError), fallback) 311 312 def __delitem__(self, key): 313 __traceback_hide__ = True 314 def action(target): 315 del target[key] 316 def fallback(target): 317 delattr(target, key) 318 self.__transparently__(action, (KeyError, IndexError, TypeError), 319 fallback) 320 321 322 class StrictAccessWrapper (AccessWrapper): 323 def __getattribute__(self, key): 324 __traceback_hide__ = True 325 def on_error(target, exc_info): 326 __traceback_hide__ = True 327 raise UndefinedError(key, target) 328 return AccessWrapper.__getattribute__(self, key, on_error) 329 330 def __getitem__(self, key): 331 __traceback_hide__ = True 332 def on_error(target, exc_info): 333 __traceback_hide__ = True 334 raise UndefinedError(key, target) 335 return AccessWrapper.__getitem__(self, key, on_error) 336 337 338 class LenientAccessWrapper (AccessWrapper): 339 def __getattribute__(self, key): 340 __traceback_hide__ = True 341 on_error = lambda target, exc_info: Undefined(key, target) 342 return AccessWrapper.__getattribute__(self, key, on_error) 343 344 def __getitem__(self, key): 345 __traceback_hide__ = True 346 on_error = lambda target, exc_info: Undefined(key, target) 347 return AccessWrapper.__getitem__(self, key, on_error) 348 239 349 240 350 class LookupBase(object): 241 351 """Abstract base class for variable lookup implementations.""" … … 246 356 """ 247 357 return { 248 358 '_lookup_name': cls.lookup_name, 249 '_lookup_attr': cls.lookup_attr, 250 '_lookup_item': cls.lookup_item 359 '_wrapper': cls.wrapper_class, 251 360 } 252 361 globals = classmethod(globals) 253 362 … … 261 370 return val 262 371 lookup_name = classmethod(lookup_name) 263 372 264 def lookup_attr(cls, data, obj, key):265 __traceback_hide__ = True266 if hasattr(obj, key):267 return getattr(obj, key)268 try:269 return obj[key]270 except (KeyError, TypeError):271 return cls.undefined(key, owner=obj)272 lookup_attr = classmethod(lookup_attr)273 274 def lookup_item(cls, data, obj, key):275 __traceback_hide__ = True276 if len(key) == 1:277 key = key[0]278 try:279 return obj[key]280 except (AttributeError, KeyError, IndexError, TypeError), e:281 if isinstance(key, basestring):282 val = getattr(obj, key, UNDEFINED)283 if val is UNDEFINED:284 return cls.undefined(key, owner=obj)285 return val286 raise287 lookup_item = classmethod(lookup_item)288 289 373 def undefined(cls, key, owner=UNDEFINED): 290 374 """Can be overridden by subclasses to specify behavior when undefined 291 375 variables are accessed. … … 320 404 321 405 :see: `StrictLookup` 322 406 """ 407 wrapper_class = LenientAccessWrapper 408 323 409 def undefined(cls, key, owner=UNDEFINED): 324 410 """Return an ``Undefined`` object.""" 325 411 __traceback_hide__ = True … … 348 434 ... 349 435 UndefinedError: {} has no member named "nil" 350 436 """ 437 wrapper_class = StrictAccessWrapper 438 351 439 def undefined(cls, key, owner=UNDEFINED): 352 440 """Raise an ``UndefinedError`` immediately.""" 353 441 __traceback_hide__ = True … … 388 476 code.co_lnotab, (), ()) 389 477 390 478 BUILTINS = __builtin__.__dict__.copy() 391 BUILTINS.update({'Markup': Markup, 'Undefined': Undefined}) 479 BUILTINS.update({'Markup': Markup, 'Undefined': Undefined, 480 'UndefinedError': UndefinedError}) 392 481 393 482 394 483 class ASTTransformer(object): … … 475 564 node.expr = self.visit(node.expr) 476 565 return node 477 566 567 def visitAssAttr(self, node): 568 node.expr = self.visit(node.expr) 569 return node 570 571 def visitAugAssign(self, node): 572 node.node = self.visit(node.node) 573 node.expr = self.visit(node.expr) 574 return node 575 478 576 def visitDecorators(self, node): 479 577 node.nodes = [self.visit(x) for x in node.nodes] 480 578 return node 481 579 580 def visitExec(self, node): 581 node.expr = self.visit(node.expr) 582 node.locals = self.visit(node.locals) 583 node.globals = self.visit(node.globals) 584 return node 585 482 586 def visitFor(self, node): 483 587 node.assign = self.visit(node.assign) 484 588 node.list = self.visit(node.list) … … 503 607 node.expr3 = self.visit(node.expr3) 504 608 return node 505 609 610 def visitReturn(self, node): 611 node.value = self.visit(node.value) 612 return node 613 506 614 def visitTryExcept(self, node): 507 615 node.body = self.visit(node.body) 508 616 node.handlers = self.visit(node.handlers) … … 536 644 node.nodes = [self.visit(x) for x in node.nodes] 537 645 return node 538 646 visitAnd = visitOr = visitBitand = visitBitor = visitBitxor = _visitBoolOp 539 visitAssTuple = _visitBoolOp647 visitAssTuple = visitAssList = _visitBoolOp 540 648 541 649 def _visitBinOp(self, node): 542 650 node.left = self.visit(node.left) … … 646 754 return ast.Const(node.value.decode('utf-8')) 647 755 return node 648 756 757 def visitAugAssign(self, node): 758 if isinstance(node.node, ast.Name): 759 name = node.node.name 760 node.node = ast.Subscript(ast.Name('data'), 'OP_APPLY', 761 [ast.Const(name)]) 762 node.expr = self.visit(node.expr) 763 # if name in data: do the augmented assignment 764 # otherwise: raise UndefinedError 765 # 766 # I don't think using an Undefined instance here would 767 # make sense, since the augmented assignment would just 768 # fail. 769 return ast.If([ 770 (ast.Compare(ast.Const(name),[('in', ast.Name('data'))]), 771 ast.Stmt([node])) 772 ], ast.Stmt([ast.Raise(ast.CallFunc(ast.Name('UndefinedError'), 773 [ast.Const(name)]), 774 None, None)])) 775 else: 776 return ASTTransformer.visitAugAssign(self, node) 777 649 778 def visitAssName(self, node): 650 779 if self.locals: 651 780 self.locals[-1].add(node.name) 652 781 return node 653 782 783 def visitAssAttr(self, node): 784 node = ASTTransformer.visitAssAttr(self, node) 785 node.expr = ast.CallFunc(ast.Name("_wrapper"), [node.expr]) 786 return node 787 654 788 def visitClass(self, node): 655 789 self.locals.append(set()) 656 790 node = ASTTransformer.visitClass(self, node) … … 676 810 return node 677 811 678 812 def visitGetattr(self, node): 679 return ast.CallFunc(ast.Name('_lookup_attr'), [ 680 ast.Name('data'), self.visit(node.expr), 681 ast.Const(node.attrname) 682 ]) 813 node = ASTTransformer.visitGetattr(self, node) 814 node.expr = ast.CallFunc(ast.Name("_wrapper"), [node.expr]) 815 return node 683 816 684 817 def visitLambda(self, node): 685 818 self.locals.append(set(flatten(node.argnames))) … … 704 837 return ast.CallFunc(ast.Name('_lookup_name'), func_args) 705 838 706 839 def visitSubscript(self, node): 707 return ast.CallFunc(ast.Name('_lookup_item'), [ 708 ast.Name('data'), self.visit(node.expr), 709 ast.Tuple([self.visit(sub) for sub in node.subs]) 710 ]) 840 node = ASTTransformer.visitSubscript(self, node) 841 node.expr = ast.CallFunc(ast.Name("_wrapper"), [node.expr]) 842 return node -
genshi/template/tests/eval.py
=== modified file 'genshi/template/tests/eval.py'
379 379 expr.evaluate({'something': Something()}) 380 380 self.fail('Expected UndefinedError') 381 381 except UndefinedError, e: 382 self.assertEqual('<Something> has no member named "nil"', str(e)) 382 383 exc_type, exc_value, exc_traceback = sys.exc_info() 384 search_string = "<Expression 'something.nil'>" 383 385 frame = exc_traceback.tb_next 384 frames = []385 386 while frame.tb_next: 386 387 frame = frame.tb_next 387 frames.append(frame)388 self.assertEqual('<Something> has no member named "nil"', str(e))389 self.assertEqual("<Expression 'something.nil'>",390 frames[-3].tb_frame.f_code.co_name)391 self.assertEqual('index.html',392 frames[-3].tb_frame.f_code.co_filename)393 self.assertEqual(50, frame s[-3].tb_lineno)388 code = frame.tb_frame.f_code 389 if code.co_name == search_string: 390 break 391 else: 392 self.fail("never found the frame I was looking for") 393 self.assertEqual('index.html', code.co_filename) 394 self.assertEqual(50, frame.tb_lineno) 394 395 395 396 def test_error_getitem_undefined_string(self): 396 397 class Something(object): … … 402 403 expr.evaluate({'something': Something()}) 403 404 self.fail('Expected UndefinedError') 404 405 except UndefinedError, e: 406 self.assertEqual('<Something> has no member named "nil"', str(e)) 405 407 exc_type, exc_value, exc_traceback = sys.exc_info() 408 search_string = '''<Expression 'something["nil"]'>''' 406 409 frame = exc_traceback.tb_next 407 frames = []408 410 while frame.tb_next: 409 411 frame = frame.tb_next 410 frames.append(frame)411 self.assertEqual('<Something> has no member named "nil"', str(e))412 self.assertEqual('''<Expression 'something["nil"]'>''',413 frames[-3].tb_frame.f_code.co_name)414 self.assertEqual('index.html',415 frames[-3].tb_frame.f_code.co_filename)416 self.assertEqual(50, frame s[-3].tb_lineno)412 code = frame.tb_frame.f_code 413 if code.co_name == search_string: 414 break 415 else: 416 self.fail("never found the frame I was looking for") 417 self.assertEqual('index.html', code.co_filename) 418 self.assertEqual(50, frame.tb_lineno) 417 419 418 420 419 421 class SuiteTestCase(unittest.TestCase): … … 504 506 suite.execute(data) 505 507 self.assertEqual(4, data['x']) 506 508 509 def test_augmented_attribute_assignment(self): 510 suite = Suite("d.k += 42") 511 d = {"k": 1} 512 suite.execute({"d": d}) 513 self.assertEqual(43, d["k"]) 514 515 def test_local_augmented_assign(self): 516 Suite("x = 1; x += 42; assert x == 43").execute({}) 517 518 def test_assign_in_list(self): 519 suite = Suite("[d.k] = 'foo',; assert d['k'] == 'foo'") 520 d = {"k": "bar"} 521 suite.execute({"d": d}) 522 self.assertEqual("foo", d["k"]) 523 524 def test_exec(self): 525 suite = Suite("x = 1; exec d.k; assert x == 42, x") 526 suite.execute({"d": {"k": "x = 42"}}) 527 528 def test_return(self): 529 suite = Suite(""" 530 def f(): 531 return v 532 533 assert f() == 42 534 """) 535 suite.execute({"v": 42}) 536 537 def test_assign_to_dict_item(self): 538 suite = Suite("d['k'] = 'foo'") 539 data = {"d": {}} 540 suite.execute(data) 541 self.assertEqual("foo", data["d"]["k"]) 542 543 def test_assign_to_attribute(self): 544 suite = Suite("d.k = 'foo'") 545 data = {"d": {}} 546 suite.execute(data) 547 self.assertEqual("foo", data["d"]["k"]) 548 549 def test_delattr(self): 550 d = {"k": "foo"} 551 Suite("del d.k").execute({"d": d}) 552 self.failIf("k" in d, `d`) 553 554 def test_delitem(self): 555 class Container (object): 556 def __init__(self): 557 self.foo = "bar" 558 c = Container() 559 Suite("del c['foo']").execute({"c": c}) 560 self.failIf(hasattr(c, "foo"), dir(c)) 561 507 562 508 563 def suite(): 509 564 suite = unittest.TestSuite()
