gchecky.controller

Package gchecky :: Module controller |
[hide private]
[frames] | no frames]

# Source Code for Module gchecky.controller

      1  from base64 import b64encode
      2  from gchecky import gxml
      3  from gchecky import model as gmodel
      4
      5 -class ProcessingException(Exception):
      6 -    def __init__(self, message, where=''):
      7          self.where = where
      8          return Exception.__init__(self, message)
      9
     10 -def html_escape(html):
     11      """
     12      A simple helper that escapes '<', '>' and '&' to make sure the text
     13      is safe to be output directly into html text flow.
     14      @param html A (unicode or not) string to be escaped from html.
     15
     16      >>> safe_str = 'Is''n it a text \"with\": some  fancy characters ;-)?!'
     17      >>> html_escape(safe_str) == safe_str
     18      True
     19      >>> html_escape('Omg&, <this> is not &a safe string <to> &htmlize >_<!')
     20      'Omg&amp;, &lt;this&gt; is not &amp;a safe string &lt;to&gt; &amp;htmlize &gt;_&lt;!'
     21      """
     22      if not isinstance(html, basestring):
     23          # Nore: html should be a string already, so don't bother applying
     24          # the correct encoding - use the system defaults.
     25          html = unicode(html)
     26      return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
     27
     28 -class html_order(object):
     29      """
     30      TODO:
     31      """
     32      cart = None
     33      signature = None
     34      url = None
     35      button = None
     36      xml = None
     37 -    def html(self):
     38          """
     39          Return the html form containing two required hidden fields
     40          and the submit button in the form of Google Checkout button image.
     41          """
     42          return """
     43                 <form method="post" action="%s">
     44                     <input type="hidden" name="cart" value="%s" />
     45                     <input type="hidden" name="signature" value="%s" />
     46                     <input type="image" src="%s" alt="Google Checkout" />
     47                 </form>
     48                 """ % (html_escape(self.url), self.cart, self.signature, html_escape(self.button))
     49
     50 -class ControllerLevel_1(object):
     51      __MERCHANT_BUTTON  = 'MERCHANT_BUTTON'
     52      __CLIENT_POST_CART = 'CLIENT_POST_CART'
     53      __SERVER_POST_CART = 'SERVER_POST_CART'
     54      __ORDER_PROCESSING = 'ORDER_PROCESSING'
     55      __CLIENT_DONATION  = 'CLIENT_DONATION'
     56      __SERVER_DONATION  = 'SERVER_DONATION'
     57      __DONATION_BUTTON  = 'DONATION_BUTTON'
     58      __SANDBOX_URLS  = {__MERCHANT_BUTTON: 'https://sandbox.google.com/checkout/buttons/checkout.gif?merchant_id=%s&w=160&h=43&style=white&variant=text',
     59                         __CLIENT_POST_CART:'https://sandbox.google.com/checkout/api/checkout/v2/checkout/Merchant/%s',
     60                         __SERVER_POST_CART:'https://sandbox.google.com/checkout/api/checkout/v2/merchantCheckout/Merchant/%s',
     61                         __ORDER_PROCESSING:'https://sandbox.google.com/checkout/api/checkout/v2/request/Merchant/%s',
     62                         __CLIENT_DONATION: 'https://sandbox.google.com/checkout/api/checkout/v2/checkout/Donations/%s',
     63                         __SERVER_DONATION: 'https://sandbox.google.com/checkout/api/checkout/v2/merchantCheckout/Donations/%s',
     64                         __DONATION_BUTTON: 'https://sandbox.google.com/checkout/buttons/donation.gif?merchant_id=%s&w=160&h=43&style=white&variant=text',
     65                        }
     66      __PRODUCTION_URLS={__MERCHANT_BUTTON: 'https://checkout.google.com/buttons/checkout.gif?merchant_id=%s&w=160&h=43&style=white&variant=text',
     67                         __CLIENT_POST_CART:'https://checkout.google.com/api/checkout/v2/checkout/Merchant/%s',
     68                         __SERVER_POST_CART:'https://checkout.google.com/api/checkout/v2/merchantCheckout/Merchant/%s',
     69                         __ORDER_PROCESSING:'https://checkout.google.com/api/checkout/v2/request/Merchant/%s',
     70                         __CLIENT_DONATION: 'https://checkout.google.com/api/checkout/v2/checkout/Donations/%s',
     71                         __SERVER_DONATION: 'https://checkout.google.com/api/checkout/v2/merchantCheckout/Donations/%s',
     72                         __DONATION_BUTTON: 'https://checkout.google.com/buttons/donation.gif?merchant_id=%s&w=160&h=43&style=white&variant=text',
     73                        }
     74      # Specify all the needed information such as merchant account credentials:
     75      #   - sandbox or production
     76      #   - google vendor ID
     77      #   - google merchant key
     78 -    def __init__(self, vendor_id, merchant_key, is_sandbox=True, currency='USD'):
     79          self.vendor_id = vendor_id
     80          self.merchant_key = merchant_key
     81          self.is_sandbox = is_sandbox
     82          self.currency = currency
     83
     84 -    def _get_url(self, tag, diagnose):
     85          urls = (self.is_sandbox and self.__SANDBOX_URLS
     86                                ) or self.__PRODUCTION_URLS
     87          if urls.has_key(tag):
     88              url = urls[tag]
     89              if diagnose:
     90                  url += '/diagnose'
     91              return url
     92          raise Exception('Unknown url tag "' + tag + '"')
     93
     94 -    def get_client_post_cart_url(self, diagnose):
     95          return self._get_url(self.__CLIENT_POST_CART, diagnose) % (self.vendor_id,)
     96
     97 -    def get_server_post_cart_url(self, diagnose):
     98          return self._get_url(self.__SERVER_POST_CART, diagnose) % (self.vendor_id,)
     99
    100 -    def get_checkout_button_url(self, diagnose):
    101          return self._get_url(self.__MERCHANT_BUTTON, diagnose) % (self.vendor_id,)
    102      get_cart_post_button = get_checkout_button_url
    103
    104 -    def get_order_processing_url(self, diagnose):
    105          return self._get_url(self.__ORDER_PROCESSING, diagnose) % (self.vendor_id,)
    106
    107 -    def get_client_donation_url(self, diagnose):
    108          return self._get_url(self.__CLIENT_DONATION, diagnose) % (self.vendor_id,)
    109
    110 -    def get_server_donation_url(self, diagnose):
    111          return self._get_url(self.__SERVER_DONATION, diagnose) % (self.vendor_id,)
    112
    113 -    def get_donation_button_url(self, diagnose):
    114          return self._get_url(self.__DONATION_BUTTON, diagnose) % (self.vendor_id,)
    115
    116 -    def create_HMAC_SHA_signature(self, xml_text):
    117          import hmac, sha
    118          return hmac.new(self.merchant_key, xml_text, sha).digest()
    119
    120      # Specify order_id to track the order
    121      # The order_id will be send back to us by google with order verification
    122 -    def prepare_order(self, order, order_id=None, diagnose=False):
    123          cart = order.toxml()
    124
    125          cart64 = b64encode(cart)
    126          signature64 = b64encode(self.create_HMAC_SHA_signature(cart))
    127          html = html_order()
    128          html.cart = cart64
    129          html.signature = signature64
    130          html.url = self.get_client_post_cart_url(diagnose)
    131          html.button = self.get_checkout_button_url(diagnose)
    132          html.xml = cart
    133          return html
    134
    135 -    def prepare_donation(self, order, order_id=None, diagnose=False):
    136          html = self.prepare_order(order, order_id, diagnose)
    137          html.url = self.get_client_donation_url(diagnose)
    138          html.button = self.get_donation_button_url(diagnose)
    139          return html
    140
    141 -class ControllerContext(object):
    142      """
    143      """
    144      # Indicates the direction: True => we call GC, False => GC calls us
    145      outgoing = True
    146      # The request XML text
    147      xml = None
    148      # The request message - one of the classes in gchecky.model module
    149      message = None
    150      # Indicates that the message being sent is diagnose message (implies outgoing=True).
    151      diagnose = False
    152      # Associated google order number
    153      order_id = None
    154      # A serial number assigned by google to this message
    155      serial = None
    156      # The response message - one of the classes in gchecky.model module
    157      response_message = None
    158      # The response XML text
    159      response_xml = None
    160
    161 -    def __init__(self, outgoing = True):
    162          self.outgoing = outgoing
    163
    164 -class GcheckyError(Exception):
    165      """
    166      Base class for exception that could be thrown by gchecky library.
    167      """
    168 -    def __init__(self, message, context, origin=None):
    169          """
    170          @param message String message describing the problem. Can't be empty.
    171          @param context An instance of gchecky.controller.ControllerContext
    172                         that describes the current request processing context.
    173                         Can't be None.
    174          @param origin The original exception that caused this exception
    175                        to be thrown if any. Could be None.
    176          """
    177          self.message = message
    178          self.context = context
    179          self.origin = origin
    180          self.traceback = None
    181          if origin is not None:
    182              from traceback import format_exc
    183              self.traceback = format_exc()
    184
    185 -    def __unicode__(self):
    186          return self.message
    187      __str__ = __unicode__
    188      __repr__ = __unicode__
    189
    190 -class DataError(GcheckyError):
    191      """
    192      An exception of this class occures whenever there is error in converting
    193      python data to/from xml.
    194      """
    195      pass
    196
    197 -class HandlerError(GcheckyError):
    198      """
    199      An exception of this class occures whenever an exception is thrown
    200      from user defined handler.
    201      """
    202      pass
    203
    204 -class SystemError(GcheckyError):
    205      """
    206      An exception of this class occures whenever there is a system error, such
    207      as network being unavailable or DB down.
    208      """
    209      pass
    210
    211 -class LibraryError(GcheckyError):
    212      """
    213      An exception of this class occures whenever there is a bug encountered
    214      in gchecky library. It represents a bug which should be reported as an issue
    215      at U{Gchecky issue tracker <http://gchecky.googlecode.com/>}.
    216      """
    217      pass
    218
    219 -class ControllerLevel_2(ControllerLevel_1):
    220 -    def on_xml_sending(self, context):
    221          """
    222          This hook is called just before sending xml to GC.
    223
    224          @param context.xml  The xml message to be sent to GC.
    225          @param context.url  The exact URL the message is about to be sent.
    226          @return     Should return nothing, because the return value is ignored.
    227          """
    228          pass
    229
    230 -    def on_xml_sent(self, context):
    231          """
    232          This hook is called right after sending xml to GC.
    233
    234          @param context.xml  The xml message to be sent to GC.
    235          @param context.url  The exact URL the message is about to be sent.
    236          @param context.response_xml The reply xml of GC.
    237          @return Should return nothing, because the return value is ignored.
    238          """
    239          pass
    240
    241 -    def on_message_sending(self, context):
    242          """
    243          This hook is called just before sending xml to GC.
    244
    245          @param context.xml  The xml message to be sent to GC.
    246          @param context.url  The exact URL the message is about to be sent.
    247          @return     Should return nothing, because the return value is ignored.
    248          """
    249          pass
    250
    251 -    def on_message_sent(self, context):
    252          """
    253          This hook is called right after sending xml to GC.
    254
    255          @param context.xml The message to be sent to GC (an instance of one
    256                     of gchecky.model classes).
    257          @param context.response_xml The reply message of GC.
    258          @return Should return nothing, because the return value is ignored.
    259          """
    260          pass
    261
    262 -    def on_xml_receiving(self, context):
    263          """
    264          This hook is called just before processing the received xml from GC.
    265
    266          @param context.xml  The xml message received from GC.
    267          @return     Should return nothing, because the return value is ignored.
    268          """
    269          pass
    270
    271 -    def on_xml_received(self, context):
    272          """
    273          This hook is called right after processing xml from GC.
    274
    275          @param context.xml  The xml message received from GC.
    276          @param context.response_xml The reply xml to GC.
    277          @return Should return nothing, because the return value is ignored.
    278          """
    279          pass
    280
    281 -    def on_message_receiving(self, context):
    282          """
    283          This hook is called just before processing the received message from GC.
    284
    285          @param context.message The message received from GC.
    286          @return     Should return nothing, because the return value is ignored.
    287          """
    288          pass
    289
    290 -    def on_message_received(self, context):
    291          """
    292          This hook is called right after processing message from GC.
    293
    294          @param context.message The message received from GC.
    295          @param context.response_message The reply object to GC (either ok_t or error_t).
    296          @return Should return nothing, because the return value is ignored.
    297          """
    298          pass
    299
    300 -    def on_retrieve_order(self, order_id, context=None):
    301          """
    302          This hook is called from message processing code just before calling
    303          the corresponding message handler.
    304          The idea is to allow user code to load order in one place and then
    305          receive the loaded object as parameter in message handler.
    306          This method should not throw if order is not found - instead it should
    307          return None.
    308
    309          @param order_id The google order number corresponding to the message
    310                  received.
    311          @return The order object that will be passed to message handlers.
    312          """
    313          pass
    314
    315 -    def handle_new_order(self, message, order_id, context, order=None):
    316          """
    317          Google sends a new order notification when a buyer places an order
    318          through Google Checkout. Before shipping the items in an order,
    319          you should wait until you have also received the risk information
    320          notification for that order as well as the order state change
    321          notification informing you that the order's financial state
    322          has been updated to 'CHARGEABLE'.
    323          """
    324          pass
    325
    326 -    def handle_order_state_change(self, message, order_id, context, order=None):
    327          pass
    328
    329 -    def handle_authorization_amount(self, message, order_id, context, order=None):
    330          pass
    331
    332 -    def handle_risk_information(self, message, order_id, context, order=None):
    333          """
    334          Google Checkout sends a risk information notification to provide
    335          financial information
    336          that helps you to ensure that an order is not fraudulent.
    337          """
    338          pass
    339
    340 -    def handle_charge_amount(self, message, order_id, context, order=None):
    341          pass
    342
    343 -    def handle_refund_amount(self, message, order_id, context, order=None):
    344          pass
    345
    346 -    def handle_chargeback_amount(self, message, order_id, context, order=None):
    347          pass
    348
    349 -    def handle_notification(self, message, order_id, context, order=None):
    350          """
    351          This handler is called when a message received from GC and when the more
    352          specific message handler was not found or returned None (which means
    353          it was not able to process the message).
    354
    355          @param message The message from GC to be processed.
    356          @param order_id The google order number for which message is sent.
    357          @param order The object loaded by on_retrieve_order(order_id) or None.
    358          @return If message was processed successfully then return gmodel.ok_t().
    359                  If an error occured when proessing, then the method should
    360                  return any other value (not-None).
    361                  If the message is of unknown type or can't be processed by
    362                  this handler then return None.
    363          """
    364          # By default return None because we don' handle anything
    365          pass
    366
    367 -    def on_exception(self, exception, context):
    368          """
    369          By default simply rethrow the exception ignoring context.
    370          Could be used for loggin all the processing errors.
    371          @param exception The exception that was caught, of (sub)type GcheckyError.
    372          @param context The request context where the exception occured.
    373          """
    374          raise exception
    375
    376 -    def __call_handler(self, handler_name, context, *args, **kwargs):
    377          if hasattr(self, handler_name):
    378              try:
    379                  handler = getattr(self, handler_name)
    380                  return handler(context=context, *args, **kwargs)
    381              except Exception, e:
    382                  error = "Exception in user handler '%s': %s" % (handler_name, e)
    383                  raise HandlerError(message=error,
    384                                     context=context,
    385                                     origin=e)
    386          error="Unknown user handler: '%s'" % (handler_name,)
    387          raise HandlerError(message=error, context=context)
    388
    389 -    def _send_xml(self, msg, context, diagnose):
    390          """
    391          The helper method that submits an xml message to GC.
    392          """
    393          context.diagnose = diagnose
    394          url = self.get_order_processing_url(diagnose)
    395          context.url = url
    396          import urllib2
    397          req = urllib2.Request(url=url, data=msg)
    398          req.add_header('Authorization',
    399                         'Basic %s' % (b64encode('%s:%s' % (self.vendor_id,
    400                                                            self.merchant_key)),))
    401          req.add_header('Content-Type', ' application/xml; charset=UTF-8')
    402          req.add_header('Accept', ' application/xml; charset=UTF-8')
    403          try:
    404              self.__call_handler('on_xml_sending', context=context)
    405              response = urllib2.urlopen(req).read()
    406              self.__call_handler('on_xml_sent', context=context)
    407              return response
    408          except urllib2.HTTPError, e:
    409              error = e.fp.read()
    410              raise SystemError(message='Error in urllib2.urlopen: %s' % (error,),
    411                                context=context,
    412                                origin=e)
    413
    414 -    def send_message(self, message, context=None, diagnose=False):
    415          if context is None:
    416              context = ControllerContext(outgoing=True)
    417          context.message = message
    418          context.diagnose = diagnose
    419
    420          if isinstance(message, gmodel.abstract_order_t):
    421              context.order_id = message.google_order_number
    422
    423          try:
    424              try:
    425                  self.__call_handler('on_message_sending', context=context)
    426                  message_xml = message.toxml()
    427                  context.xml = message_xml
    428              except Exception, e:
    429                  error = "Error converting message to xml: '%s'" % (unicode(e), )
    430                  raise DataError(message=error, context=context, origin=e)
    431              response_xml = self._send_xml(message_xml, context=context, diagnose=diagnose)
    432              context.response_xml = response_xml
    433
    434              response = self.__process_message_result(response_xml, context=context)
    435              context.response_message = response
    436
    437              self.__call_handler('on_message_sent', context=context)
    438              return response
    439          except GcheckyError, e:
    440              return self.on_exception(exception=e, context=context)
    441
    442 -    def __process_message_result(self, response_xml, context):
    443          try:
    444              doc = gxml.Document.fromxml(response_xml)
    445          except Exception, e:
    446              error = "Error converting message to xml: '%s'" % (unicode(e), )
    447              raise LibraryError(message=error, context=context, origin=e)
    448
    449          if context.diagnose:
    450              # It has to be a 'diagnosis' response, otherwise... omg!.. panic!...
    451              if doc.__class__ != gmodel.diagnosis_t:
    452                  error = "The response has to be of type diagnosis_t, not '%s'" % (doc.__class__,)
    453                  raise LibraryError(message=error,
    454                                     context=context)
    455              return doc
    456
    457          # If the response is 'ok' or 'bye' just return, because its good
    458          if doc.__class__ == gmodel.request_received_t:
    459              return doc
    460
    461          if doc.__class__ == gmodel.bye_t:
    462              return doc
    463
    464          # It's not 'ok' so it has to be 'error', otherwise it's an error
    465          if doc.__class__ != gmodel.error_t:
    466              error = "Unknown response type (expected error_t): '%s'" % (doc.__class__,)
    467              raise LibraryError(message=error, context=context)
    468
    469          # 'error' - process it by throwing an exception with error/warning text
    470          msg = 'Error message from GCheckout API:\n%s' % (doc.error_message, )
    471          if doc.warning_messages:
    472              tmp = ''
    473              for warning in doc.warning_messages:
    474                  tmp += '\n%s' % (warning,)
    475              msg += ('Additional warnings:%s' % (tmp,))
    476          raise DataError(message=msg, context=context)
    477
    478 -    def hello(self):
    479          context = ControllerContext()
    480          doc = self.send_message(gmodel.hello_t(), context)
    481          if isinstance(doc, gxml.Document) and (doc.__class__ != gmodel.bye_t):
    482              error = "Expected <bye/> but got %s" % (doc.__class__,)
    483              raise LibraryError(message=error, context=context, origin=e)
    484
    485 -    def archive_order(self, order_id):
    486          self.send_message(
    487              gmodel.archive_order_t(google_order_number=order_id))
    488
    489 -    def unarchive_order(self, order_id):
    490          self.send_message(
    491              gmodel.unarchive_order_t(google_order_number=order_id))
    492
    493 -    def send_buyer_message(self, order_id, message):
    494          self.send_message(gmodel.send_buyer_message_t(
    495              google_order_number = order_id,
    496              message = message,
    497              send_email = True
    498              ))
    499
    500 -    def add_merchant_order_number(self, order_id, merchant_order_number):
    501          self.send_message(gmodel.add_merchant_order_number_t(
    502              google_order_number = order_id,
    503              merchant_order_number = merchant_order_number
    504              ))
    505
    506 -    def add_tracking_data(self, order_id, carrier, tracking_number):
    507          self.send_message(gmodel.add_tracking_data_t(
    508              google_order_number = order_id,
    509              tracking_data = gmodel.tracking_data_t(carrier         = carrier,
    510                                                     tracking_number = tracking_number)
    511              ))
    512
    513 -    def charge_order(self, order_id, amount):
    514          self.send_message(gmodel.charge_order_t(
    515              google_order_number = order_id,
    516              amount = gmodel.price_t(value = amount, currency = self.currency)
    517              ))
    518
    519 -    def refund_order(self, order_id, amount, reason, comment=None):
    520          self.send_message(gmodel.refund_order_t(
    521              google_order_number = order_id,
    522              amount = gmodel.price_t(value = amount, currency = self.currency),
    523              reason = reason,
    524              comment = comment or None
    525              ))
    526
    527 -    def authorize_order(self, order_id):
    528          self.send_message(gmodel.authorize_order_t(
    529              google_order_number = order_id
    530          ))
    531
    532 -    def cancel_order(self, order_id, reason, comment=None):
    533          self.send_message(gmodel.cancel_order_t(
    534              google_order_number = order_id,
    535              reason = reason,
    536              comment = comment or None
    537              ))
    538
    539 -    def process_order(self, order_id):
    540          self.send_message(gmodel.process_order_t(
    541              google_order_number = order_id
    542              ))
    543
    544 -    def deliver_order(self, order_id,
    545                        carrier = None, tracking_number = None,
    546                        send_email = None):
    547          tracking = None
    548          if carrier or tracking_number:
    549              tracking = gmodel.tracking_data_t(carrier         = carrier,
    550                                                tracking_number = tracking_number)
    551          self.send_message(gmodel.deliver_order_t(
    552              google_order_number = order_id,
    553              tracking_data = tracking,
    554              send_email = send_email or None
    555              ))
    556
    557      # This method gets a string and returns a string
    558 -    def receive_xml(self, input_xml, context=None):
    559          if context is None:
    560              context = ControllerContext(outgoing=False)
    561          context.xml = input_xml
    562          try:
    563              self.__call_handler('on_xml_receiving', context=context)
    564              try:
    565                  input = gxml.Document.fromxml(input_xml)
    566                  context.message = input
    567              except Exception, e:
    568                  error = 'Error reading XML: %s' % (e,)
    569                  raise DataError(message=error, context=context, origin=e)
    570
    571              result = self.receive_message(message=input,
    572                                            order_id=input.google_order_number,
    573                                            context=context)
    574              context.response_message = result
    575
    576              try:
    577                  response_xml = result.toxml()
    578                  context.response_xml = response_xml
    579              except Exception, e:
    580                  error = 'Error reading XML: %s' % (e,)
    581                  raise DataError(message=error, context=context, origin=e)
    582              self.__call_handler('on_xml_received', context=context)
    583              return response_xml
    584          except GcheckyError, e:
    585              return self.on_exception(exception=e, context=context)
    586
    587      # A dictionary of document handler names. Comes handy in receive_message.
    588      __MESSAGE_HANDLERS = {
    589          gmodel.new_order_notification_t:            'handle_new_order',
    590          gmodel.order_state_change_notification_t:   'handle_order_state_change',
    591          gmodel.authorization_amount_notification_t: 'handle_authorization_amount',
    592          gmodel.risk_information_notification_t:     'handle_risk_information',
    593          gmodel.charge_amount_notification_t:        'handle_charge_amount',
    594          gmodel.refund_amount_notification_t:        'handle_refund_amount',
    595          gmodel.chargeback_amount_notification_t:    'handle_chargeback_amount',
    596          }
    597
    598 -    def receive_message(self, message, order_id, context):
    599          context.order_id = order_id
    600          self.__call_handler('on_message_receiving', context=context)
    601          # retreive order instance from DB given the google order number
    602          order = self.__call_handler('on_retrieve_order', context=context, order_id=order_id)
    603
    604          # handler = None
    605          result = None
    606          if self.__MESSAGE_HANDLERS.has_key(message.__class__):
    607              handler_name = self.__MESSAGE_HANDLERS[message.__class__]
    608              result = self.__call_handler(handler_name,
    609                                           message=message,
    610                                           order_id=order_id,
    611                                           context=context,
    612                                           order=order)
    613
    614          if result is None:
    615              result = self.__call_handler('handle_notification',
    616                                           message=message,
    617                                           order_id=order_id,
    618                                           context=context,
    619                                           order=order)
    620
    621          error = None
    622          if result is None:
    623              error = "Notification '%s' was not handled" % (message.__class__,)
    624          elif not (result.__class__ is gmodel.ok_t):
    625              try:
    626                  error = unicode(result)
    627              except Exception, e:
    628                  error = "Invalid value returned by handler '%s': %s" % (handler_name,
    629                                                                          e)
    630                  raise HandlerError(message=error, context=context, origin=e)
    631
    632          if error is not None:
    633              result = gmodel.error_t(serial_number = 'error',
    634                                      error_message=error)
    635          else:
    636              # TODO: Remove this after testing
    637              assert result.__class__ is gmodel.ok_t
    638
    639          self.__call_handler('on_message_received', context=context)
    640          return result
    641
    642  # Just an alias with a shorter name.
    643  Controller = ControllerLevel_2
    644
    645  if __name__ == "__main__":
    646 -    def run_doctests():
    647          import doctest
    648          doctest.testmod()
    649      run_doctests()
    650