Changeset 1003

Show
Ignore:
Timestamp:
02/11/08 21:42:26 (11 months ago)
Author:
nick
Message:

Merging source:pyamf/branches/error-api-185. Fixes #185 & #167

Location:
pyamf/trunk
Files:
7 modified

Legend:

Unmodified
Added
Removed
  • pyamf/trunk/CHANGES.txt

    r961 r1003  
    66of PyAMF. 
    77 
    8 0.1rc1 (unreleased) 
     80.1 (unreleased) 
    99------------------- 
    1010 
     11 - a new error handling api useful for registering custom exception classes 
     12   (Ticket:185) 
     13 - when a client receives a remoting error, an exception is generated (Ticket:167) 
    1114 - expose_request per service control vastly improved (Ticket:169) 
    1215 - Authentication per service control vastly improved (Ticket:166) 
  • pyamf/trunk/pyamf/__init__.py

    r975 r1003  
    4444TYPE_MAP = {} 
    4545 
     46#: Maps error classes to string codes 
     47ERROR_CLASS_MAP = {} 
     48 
    4649#: Specifies that objects are serialized using AMF for ActionScript 1.0 and 2.0. 
    4750AMF0 = 0 
     
    8992    """ 
    9093 
    91 class EOStream(DecodeError): 
     94class EOStream(BaseError): 
    9295    """ 
    9396    Raised if the data stream has come to a natural end. 
     
    992995    return declaration 
    993996 
     997def add_error_class(klass, code): 
     998    """ 
     999    Maps an exception class to a string code. Used to map remoting onStatus 
     1000    objects to an exception class so that an exception can be built to 
     1001    represent that error. 
     1002 
     1003    class AuthenticationError(Exception): 
     1004        pass 
     1005 
     1006    An example: add_error_class(AuthenticationError, 'Auth.Failed') 
     1007    """ 
     1008    if not isinstance(code, basestring): 
     1009        code = str(code) 
     1010 
     1011    if not isinstance(klass, (type, types.ClassType)): 
     1012        raise TypeError, "klass must be a class type" 
     1013 
     1014    mro = util.get_mro(klass) 
     1015 
     1016    if not Exception in util.get_mro(klass): 
     1017        raise TypeError, 'error classes must subclass the __builtin__.Exception class' 
     1018 
     1019    if code in ERROR_CLASS_MAP.keys(): 
     1020        raise ValueError, 'Code %s is already registered' % code 
     1021 
     1022    ERROR_CLASS_MAP[code] = klass 
     1023 
     1024def remove_error_class(klass): 
     1025    """ 
     1026    Removes a class from ERROR_CLASS_MAP 
     1027    """ 
     1028    if isinstance(klass, basestring): 
     1029        if not klass in ERROR_CLASS_MAP.keys(): 
     1030            raise ValueError, 'Code %s is not registered' % klass 
     1031    elif isinstance(klass, (type, types.ClassType)): 
     1032        classes = ERROR_CLASS_MAP.values() 
     1033        if not klass in classes: 
     1034            raise ValueError, 'Class %s is not registered' % klass 
     1035 
     1036        klass = ERROR_CLASS_MAP.keys()[classes.index(klass)] 
     1037    else: 
     1038        raise TypeError, "Invalid type, expected class or string" 
     1039 
     1040    del ERROR_CLASS_MAP[klass] 
     1041 
    9941042register_adapters() 
  • pyamf/trunk/pyamf/remoting/__init__.py

    r946 r1003  
    5151CONTENT_TYPE = 'application/x-amf' 
    5252 
     53ERROR_CALL_FAILED, = range(1) 
     54ERROR_CODES = { 
     55    ERROR_CALL_FAILED: 'Server.Call.Failed' 
     56} 
     57 
    5358class RemotingError(pyamf.BaseError): 
    5459    """ 
    5560    Generic remoting error class. 
    5661    """ 
     62 
     63class RemotingCallFailed(RemotingError): 
     64    """ 
     65    Raised if Server.Call.Failed received 
     66    """ 
     67 
     68pyamf.add_error_class(RemotingCallFailed, ERROR_CODES[ERROR_CALL_FAILED]) 
    5769 
    5870class HeaderCollection(dict): 
     
    233245        return x + '>' 
    234246 
     247    def raiseException(self): 
     248        """ 
     249        Raises an exception based on the fault object. There is no traceback 
     250        available. 
     251        """ 
     252        raise get_exception_from_fault(self), self.description, None 
     253 
    235254pyamf.register_class(BaseFault, 
    236255    attrs=['level', 'code', 'type', 'details', 'description']) 
     
    243262    level = 'error' 
    244263 
    245 pyamf.register_class(ErrorFault, 
    246     attrs=['level', 'code', 'type', 'details', 'description']) 
     264pyamf.register_class(ErrorFault) 
    247265 
    248266def _read_header(stream, decoder, strict=False): 
     
    443461    return STATUS_CODES[status] 
    444462 
    445 def get_fault_class(level): 
     463def get_fault_class(level, **kwargs): 
     464    code = kwargs.get('code', ) 
    446465    if level == 'error': 
    447466        return ErrorFault 
     
    464483            e[x] = y 
    465484 
    466     return get_fault_class(level)(**e) 
     485    return get_fault_class(level, **e)(**e) 
    467486 
    468487def decode(stream, context=None, strict=False): 
     
    569588 
    570589    return stream 
     590 
     591def get_exception_from_fault(fault): 
     592    # XXX nick: threading problems here? 
     593    try: 
     594        return pyamf.ERROR_CLASS_MAP[fault.code] 
     595    except KeyError: 
     596        # default to RemotingError 
     597        return RemotingError 
  • pyamf/trunk/pyamf/remoting/client/__init__.py

    r960 r1003  
    144144        self.response = response 
    145145        self.result = self.response.body 
     146 
     147        if isinstance(self.result, remoting.ErrorFault): 
     148            self.result.raiseException() 
    146149 
    147150    def _get_result(self): 
  • pyamf/trunk/pyamf/tests/test_basic.py

    r853 r1003  
    612612        self.assertEquals(td, td2) 
    613613 
     614class ErrorClassMapTestCase(unittest.TestCase): 
     615    """ 
     616    I test all functionality related to manipulating L{pyamf.ERROR_CLASS_MAP} 
     617    """ 
     618 
     619    def setUp(self): 
     620        self.map_copy = pyamf.ERROR_CLASS_MAP.copy() 
     621 
     622    def tearDown(self): 
     623        pyamf.ERROR_CLASS_MAP = self.map_copy 
     624 
     625    def test_add(self): 
     626        class A: 
     627            pass 
     628 
     629        class B(Exception): 
     630            pass 
     631 
     632        self.assertRaises(TypeError, pyamf.add_error_class, None, 'a') 
     633 
     634        # class A does not sub-class Exception 
     635        self.assertRaises(TypeError, pyamf.add_error_class, A, 'a') 
     636 
     637        pyamf.add_error_class(B, 'b') 
     638        self.assertEquals(pyamf.ERROR_CLASS_MAP['b'], B) 
     639 
     640        pyamf.add_error_class(B, 'a') 
     641        self.assertEquals(pyamf.ERROR_CLASS_MAP['a'], B) 
     642 
     643        class C(Exception): 
     644            pass 
     645 
     646        self.assertRaises(ValueError, pyamf.add_error_class, C, 'b') 
     647 
     648    def test_remove(self): 
     649        class B(Exception): 
     650            pass 
     651 
     652        pyamf.ERROR_CLASS_MAP['abc'] = B 
     653 
     654        self.assertRaises(TypeError, pyamf.remove_error_class, None) 
     655 
     656        pyamf.remove_error_class('abc') 
     657        self.assertFalse('abc' in pyamf.ERROR_CLASS_MAP.keys()) 
     658        self.assertRaises(KeyError, pyamf.ERROR_CLASS_MAP.__getitem__, 'abc') 
     659 
     660        pyamf.ERROR_CLASS_MAP['abc'] = B 
     661 
     662        pyamf.remove_error_class(B) 
     663 
     664        self.assertRaises(KeyError, pyamf.ERROR_CLASS_MAP.__getitem__, 'abc') 
     665        self.assertRaises(ValueError, pyamf.remove_error_class, B) 
     666        self.assertRaises(ValueError, pyamf.remove_error_class, 'abc') 
     667 
    614668def suite(): 
    615669    suite = unittest.TestSuite() 
     
    623677    suite.addTest(unittest.makeSuite(ClassLoaderTestCase)) 
    624678    suite.addTest(unittest.makeSuite(TypeMapTestCase)) 
     679    suite.addTest(unittest.makeSuite(ErrorClassMapTestCase)) 
    625680 
    626681    return suite 
  • pyamf/trunk/pyamf/tests/test_remoting.py

    r847 r1003  
    270270            '\x00\x0c\n\x00\x00\x00\x01\x02\x00\x04spam') 
    271271 
     272class FaultTestCase(unittest.TestCase): 
     273    def test_exception(self): 
     274        x = remoting.get_fault({'level': 'error', 'code': 'Server.Call.Failed'}) 
     275 
     276        self.assertRaises(remoting.RemotingCallFailed, x.raiseException) 
     277 
    272278def suite(): 
    273279    """ 
     
    279285    suite.addTest(unittest.makeSuite(EncoderTestCase)) 
    280286    suite.addTest(unittest.makeSuite(StrictEncodingTestCase)) 
     287    suite.addTest(unittest.makeSuite(FaultTestCase)) 
    281288 
    282289    from pyamf.tests.remoting import test_client, test_remoteobject 
  • pyamf/trunk/pyamf/tests/test_sol.py

    r973 r1003  
    1212""" 
    1313 
    14 import unittest, os, os.path 
     14import unittest, os, os.path, warnings 
    1515 
    1616import pyamf 
    1717from pyamf import amf0, util, sol 
     18 
     19warnings.simplefilter('ignore', RuntimeWarning) 
    1820 
    1921class DecoderTestCase(unittest.TestCase):