Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SOAP call response parsing failing with pydantic models #1093

Open
mmakaay opened this issue Nov 14, 2024 · 2 comments
Open

SOAP call response parsing failing with pydantic models #1093

mmakaay opened this issue Nov 14, 2024 · 2 comments

Comments

@mmakaay
Copy link

mmakaay commented Nov 14, 2024

Context

I am currently investigating if I can use xsdata for consuming a SOAP service that I have to integrate with. So far, I see a lot of good stuff. I'm using xsdata-pydantic, and am very happy with the generated schema classes. I was quickly able to build a request to send using these.

Issue

Parsing the response using the built-in Client fails. When using client.send(...), validation fails on the output message that is returned from the service:

1 validation error for Body
fault
  Field required [type=missing, input_value={'can_response': CanRespo...cel.'), request_uid='')}, input_type=dict]
    For further information visit https://errors.pydantic.dev/2.9/v/missing

It tells me that the fault field is required. The service that I am talking to however, does not define any faults for any of the operations. Here's the operation that results in the above validation error.

<wsdl:operation name="can">
	<wsdl:input message="odfs:canRequest"/>
	<wsdl:output message="odfs:canResponse"/>
</wsdl:operation>

The xsdata code generation adds the fault attribute to the expected schema.

Possible issue

When I disable the two lines of code at

https://github.com/tefra/xsdata/blob/main/xsdata/codegen/mappers/definitions.py#L219-L220

and generate fresh code, I end up with a working client that can process the response data.

Maybe, the build_envelope_fault(...) method needs something like this at its beginning, to not generate a wsdl:Fault when no faults are defined?

        if not port_type_operation.faults:
            return

When the Fault needs to be added, then possibly the issue arises because the Fault is defined as a required Field?

My current work-around

It's a bit of a kludge, but right now I am using xsdata-generated classes for building and parsing messages, while using Zeep's Client for handling the actual HTTP request. This looks somewhat like:

    # Build request data using xsdata-generated classes
    header = Header(...)   
    
    # Send request using Zeep.
    result = zeep_service.can(
        header=xsdata_to_dict(header),  # uses xsdata's JsonSerializer to build a dict, so Zeep can consume it
        externalOrderUid="MY-TEST-ORDER-1235",
        code="SOMETHING",
    )
    
    # Use Zeep's serializer to create a dict out of the response
    serialized = serialize_object(result, target_cls=dict)  
    
    # Using xsdata's JSON parser to map the dict to xsdata-generated classes.
    response = json_parser.decode(serialized, CanResponse)

This works (also showing that Zeep is able to handle the deserialization of the response), but it is far from ideal.

@mmakaay
Copy link
Author

mmakaay commented Nov 14, 2024

Here's a way to reproduce the issue:

Create an empty directory.
Inside that directory, generate classes for a simple public SOAP service:

$ mkdir test-xsdata-client
$ cd test-xsdata-client
$ xsdata --output pydantic 'https://www.dataaccess.com/webservicesserver/NumberConversion.wso?WSDL'

In the same directory, add the following testscript:

#!/usr/bin/env python3

from xsdata.formats.dataclass.client import Client, Config
from xsdata.formats.dataclass.context import XmlContext
from xsdata.formats.dataclass.parsers import XmlParser
from xsdata.formats.dataclass.serializers import XmlSerializer

from generated import *

context = XmlContext(class_type="pydantic")
serializer = XmlSerializer(context=context)
parser = XmlParser(context=context)

client = Client(
    config=Config.from_service(NumberConversionSoapTypeNumberToWords),
    parser=parser,
    serializer=serializer
)

response = client.send(
    NumberConversionSoapTypeNumberToWordsInput(
        body=NumberConversionSoapTypeNumberToWordsInput.Body(
            number_to_words=NumberToWords(ubi_num=1234)
        )
    )
)

print(response)

Actual output

When running this script, send() fails with a "fault field required" error.

Expected output

body=Body(number_to_words_response=NumberToWordsResponse(number_to_words_result='one thousand two hundred and thirty four '))

@mmakaay
Copy link
Author

mmakaay commented Nov 14, 2024

After generating dataclasses instead of pydantic classes, and after switching to the default XML serializer and parser, the test code worked just fine. Thus, the issue only shows up with Pydantic.

this might suggest that the bug report should have been filed under the xsdata-pydantic repository. Leaving it in here though, since the code that adds the wsdl:Fault is part of the xsdata repository code.

@mmakaay mmakaay changed the title SOAP call response parsing fails for operation without fault definition(s) SOAP call response parsing failing with pydantic models Nov 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant