Как изящно обрабатывать исключения Spyne XSD

Всякий раз, когда мое приложение Spyne получает запрос, выполняется проверка XSD. Это хорошо, но всякий раз, когда происходит нарушение XSD, возникает ошибка, и мое приложение возвращает Client.SchemaValidationError следующим образом:

<soap11env:Fault>
    <faultcode>soap11env:Client.SchemaValidationError</faultcode>
    <faultstring>:25:0:ERROR:SCHEMASV:SCHEMAV_CVC_DATATYPE_VALID_1
TNS = "http://services.so.example.org"

class InMessageType(ComplexModel):

    __namespace__ = TNS

    class Attributes(ComplexModel.Attributes):
        declare_order = 'declared'

    field_one = Unicode(values=["ONE", "TWO"],
                      min_occurs=1)
    field_two = Unicode(20, min_occurs=1)
    field_three = Unicode(20, min_occurs=0)
    Confirmation = Unicode(values=["ACCEPTED", "REJECTED"], min_occurs=1)
    FileReason = Unicode(200, min_occurs=0)
    DateTimeStamp = DateTime(min_occurs=1)


class OperationOneResponse(ComplexModel):

    __namespace__ = TNS

    class Attributes(ComplexModel.Attributes):
        declare_order = 'declared'

    ResponseMessage = Unicode(values=["SUCCESS", "FAILURE"], min_occurs=1)
    Details = Unicode(min_len=0, max_len=2000)


class ServiceOne(ServiceBase):

    @rpc(InMessageType,
         _returns=OperationOneResponse,
         _out_message_name='OperationOneResponse',
         _in_message_name='InMessageType',
         _body_style='bare',
         )
    def OperationOne(ctx, message):
        # DO STUFF HERE
        # e.g. return {'ResponseMessage': Failure, 'Details': XSDValidationError}


application = Application([ServiceOne],
                          TNS,
                          in_protocol=Soap11(validator='lxml'),
                          out_protocol=Soap11(),
                          name='ServiceOne',)


wsgi_application = WsgiApplication(application)

if __name__ == '__main__':
    pass
1: Element '{http://services.sp.pas.ng.org}DateTimeStamp': '2018-07-25T13:01' is not a valid value of the atomic type 'xs:dateTime'.</faultstring> <faultactor></faultactor> </soap11env:Fault>

Я хотел бы знать, как изящно обрабатывать ошибку проверки схемы и возвращать подробности в поле «Подробности» моей службы out_message, а не просто вызывать стандартный Client.SchemaValidationError. Я хочу сохранить сведения об ошибке в виде переменной и передать ее в свою функцию OperationOne.

Вот мой код, я изменил имена переменных для чувствительности.

TNS = "http://services.so.example.org"

class InMessageType(ComplexModel):

    __namespace__ = TNS

    class Attributes(ComplexModel.Attributes):
        declare_order = 'declared'

    field_one = Unicode(values=["ONE", "TWO"],
                      min_occurs=1)
    field_two = Unicode(20, min_occurs=1)
    field_three = Unicode(20, min_occurs=0)
    Confirmation = Unicode(values=["ACCEPTED", "REJECTED"], min_occurs=1)
    FileReason = Unicode(200, min_occurs=0)
    DateTimeStamp = DateTime(min_occurs=1)


class OperationOneResponse(ComplexModel):

    __namespace__ = TNS

    class Attributes(ComplexModel.Attributes):
        declare_order = 'declared'

    ResponseMessage = Unicode(values=["SUCCESS", "FAILURE"], min_occurs=1)
    Details = Unicode(min_len=0, max_len=2000)


class ServiceOne(ServiceBase):

    @rpc(InMessageType,
         _returns=OperationOneResponse,
         _out_message_name='OperationOneResponse',
         _in_message_name='InMessageType',
         _body_style='bare',
         )
    def OperationOne(ctx, message):
        # DO STUFF HERE
        # e.g. return {'ResponseMessage': Failure, 'Details': XSDValidationError}


application = Application([ServiceOne],
                          TNS,
                          in_protocol=Soap11(validator='lxml'),
                          out_protocol=Soap11(),
                          name='ServiceOne',)


wsgi_application = WsgiApplication(application)

if __name__ == '__main__':
    pass

Я рассмотрел следующий подход, но пока не могу заставить его работать:

  1. создать подкласс MyApplication с переопределенной функцией call_wrapper().
  2. Создайте приложение с помощью in_protocol=Soap11(validator=None)
  3. Внутри оболочки вызова установите протокол Soap11 (validator = 'lxml') и (каким-то образом) вызовите что-то, что проверит сообщение. Оберните это в блок try/except и в случае ошибки поймайте ошибку и обработайте ее любым необходимым способом.

Я просто не понял, что я могу вызвать внутри моей переопределенной функции call_wrapper(), которая фактически выполнит проверку. Я пробовал protocol.decompose_incoming_envelope() и другие подобные вещи, но пока безуспешно.


person teebagz    schedule 31.07.2019    source источник


Ответы (1)


Переопределение call_wrapper не сработает, поскольку ошибка проверки возникает до ее вызова.

Вместо этого вы должны использовать подсистему событий. В частности, вы должны зарегистрировать обработчик уровня приложения для события method_exception_object.

Вот пример:

def _on_exception_object(ctx):
    if isinstance(ctx.out_error, ValidationError):
        ctx.out_error = NicerValidationError(...)


app = Application(...)
app.event_manager.add_listener('method_exception_object', _on_exception_object)

См. этот тест для получения дополнительной информации: https://github.com/arskom/spyne/blob/4a74cfdbc7db7552bc89c0e5d5c19ed5d0755bc7/spyne/test/test_service.py#L69


Согласно вашему разъяснению, если вы не хотите отвечать с более приятной ошибкой, а обычным ответом, я боюсь, что Spyne не предназначен для удовлетворения этого варианта использования. «Преобразование» состояния обработки запроса с ошибкой в ​​обычное без необходимости усложнило бы и без того сложную логику обработки запросов.

Вместо этого вы можете ВЗЛОМАТЬ ответный документ.

Один из способов сделать это — реализовать дополнительный обработчик событий method_exception_document, в котором тег <Fault> и его содержимое либо редактируются по вашему вкусу, либо даже заменяются местами.

С верхней части моей головы:

class ValidationErrorReport(ComplexModel):
    _type_info = [
        ('foo', Unicode), 
        ('bar', Integer32),
    ]

def _on_exception_document(ctx):
    fault_elt, = ctx.out_document.xpath("//soap11:Fault", namespaces={'soap11': NS_SOAP11_ENV})
    explanation_elt = get_object_as_xml(ValidationErrorReport(...))
    fault_parent = fault_elt.parent()
    fault_parent.remove(fault_elt)
    fault_parent.add(explanation_elt)

Вышеприведенное нужно перепроверить с соответствующими API-интерфейсами Spyne и lxml (возможно, вы можете использовать find() вместо xpath()), но вы поняли идею.

Надеюсь, это поможет!

person Burak Arslan    schedule 07.08.2019
comment
Здравствуйте! Большое спасибо, что нашли время ответить на этот вопрос. Я пробовал оба подхода. Я не уверен, почему, но событие _on_method_exception не запускается в случае ошибки. Я добавил прослушиватель для каждого возможного события приложения и wsgiapplication в случае действительного запроса, который я получаю ›_on_context_created ›_on_wsgi_call ›_on_call ›_on_return_object ›_on_wsgi_return ›_on_context_closed ›_on_wsgi_close - person teebagz; 07.08.2019
comment
Итак, там, где есть SchemaValidationError, кажется, что переопределенная функция call_wrapper никогда не вызывается, а событие method_exception никогда не запускается. Я еще раз взгляну на события службы и протокола, чтобы увидеть, что я могу найти. - person teebagz; 07.08.2019
comment
Теперь я добавил прослушиватель для каждого возможного события во всех четырех классах, которые поддерживают события запуска, кажется, что только четыре события, которые запускаются в случае ошибки проверки xsd, это _on_context_created, _on_wgi_call, _on_context_closed и _on_wsgi_close - это когда у меня есть валидатор установлен на «lxml» - person teebagz; 07.08.2019
comment
Это ошибка. Пожалуйста, попробуйте еще раз с помощью spyne-2.13.11-alpha - person Burak Arslan; 08.08.2019
comment
Действительно, все правильные события, похоже, срабатывают в этой версии, спасибо. Я думаю, может быть, я не совсем понял, что я пытаюсь сделать. Я не просто хочу дать более красивую ошибку проверки внутри элемента ошибки мыла. Я хочу избежать отправки элемента сбоя мыла и вместо этого отправлять сведения об ошибке внутри ResponseType, который я определил. Я думаю (возможно, я ошибаюсь, потому что я далек от эксперта), это та часть, которая противоречит стандарту SOAP, потому что ошибки должны быть указаны как ошибки, но я работаю по спецификации, сделанной кем-то другим. - person teebagz; 08.08.2019
comment
Преобразование состояния обработки запроса с ошибкой в ​​обычное невозможно. Я бы не принял патч, добавляющий такую ​​функциональность в Spyne. Смотрите мой обновленный ответ. - person Burak Arslan; 08.08.2019
comment
Спасибо. Если я правильно понимаю, то комментарии между строками 92 и 98 точно объясняют, почему это невозможно. Как я уже упоминал, я работаю над чьей-то спецификацией, и я уже вернулся к ним, чтобы объяснить, что у меня есть оговорки относительно предоставления ответов об ошибках на их пути. Теперь я точно знаю, как объяснить, почему так поступать неправильно. Спасибо, что помогли мне, кстати, я думаю, что Spyne потрясающий, вы делаете отличную работу! - person teebagz; 08.08.2019
comment
Будет ли это похоже на проверку, вызванную полем Unicode с такими вариантами выбора, как choicefield = unicode(choices=['a', 'b']), но запрос содержит <choicefield>c</choicefield>? - person teebagz; 08.08.2019