Zeep SOAP Client: Fields Missing in Response of National rail SOAP api

120 views
Skip to first unread message

Paul Absmanner

unread,
Jun 12, 2025, 11:35:56 AM6/12/25
to A gathering place for the Open Rail Data community
Short summary:
 I'm using Zeep pip package to parse the National Rail SOAP API, and some fields (like the operator name/code) are showing up as None in the parsed response. The raw XML response clearly contains these values, so the data is there. I saw some debug logs about  UnresolvedCustomType , but Zeep eventually resolves those types when accessed manually via client.wsdl.types.get_type()  .  
  
Has anyone else encountered a similar issue where Zeep silently drops or nulls out fields that do exist in the raw XML?  Zeep Version: 4.3.1


More details:
We use Zeep as a SOAP client to parse the response of the National rail SOAP api. In the response some fields are missing, most importantly the rail provider.
Snippet of raw XMl response part:
<ns2:operator> <ns3:code>XX</ns3:code> <ns3:name>sample Rail Provider name</ns3:name> </ns2:operator> 

However, after Zeep parses the raw XML to json object, a lot of type values are None.
After enabling debug logs in zeep, it looked likeZeep SOAP client fails with  UnresolvedCustomType error due to missing schemaLocation attributes in WSDL <xsd:import> statements.
DEBUG:zeep.xsd.visitor:Ignoring import statement for namespace 'http://www...' (missing schemaLocation)

DEBUG:zeep.xsd.schema:register_type('{http://www...}OperatorMode', <UnresolvedCustomType(qname='{http://www...}OperatorMode', base_type=<UnresolvedType(qname='{http://www.w3.org/2001/XMLSchema}NMTOKEN')>)>)

At first, I suspected that due to the missing  schemaLocation   attribute in the WSDL, Zeep was ignoring the  <xsd:import>   statements. As a result, it couldn't resolve custom types defined in the imported namespaces, which I thought was preventing it from properly populating those fields in the parsed response.  
It turned out that those types are just temporarily unresolved due to schema load order.
Zeep correctly resolves these types later during parsing, because the following worked:

python: client.wsdl.types.get_type('{http://www...}OperatorMode')
This returned a valid type instance, confirming successful resolution: output: OperatorMode(value)

Question:
Has anyone else encountered a similar issue where Zeep silently drops or nulls out fields that do exist in the raw XML? Does someone know a good alternative instead of using Zeep, since the problem is likely located inside the package?

Peter Hicks

unread,
Jun 12, 2025, 11:42:43 AM6/12/25
to openrail...@googlegroups.com
Hello

On Thursday, 12 June 2025 at 16:36, 'Paul Absmanner' via A gathering place for the Open Rail Data community <openrail...@googlegroups.com> wrote:

I'm using Zeep pip package to parse the National Rail SOAP API, and some fields (like the operator name/code) are showing up as None in the parsed response. The raw XML response clearly contains these values, so the data is there. I saw some debug logs about UnresolvedCustomType , but Zeep eventually resolves those types when accessed manually via client.wsdl.types.get_type() .
Has anyone else encountered a similar issue where Zeep silently drops or nulls out fields that do exist in the raw XML? Zeep Version: 4.3.1

First of all, thank you for your excellent explanation of the problem, the high-level summary and the low-level detail.

Do you have a minimal example that you can share so others can reproduce the problem?  For example, although I don't regularly use Python, I might be able to spot the error given an example.

One option to save you from the 2000s-ness of SOAP is to use one of the Live Departure Boards products on the Rail Data Marketplace.  For example, https://raildata.org.uk/dataProduct/P-2eec03eb-4d53-4955-8a96-0314964a4e9e/overview looks like it has a much nicer HTTP front-end to it.


Peter

Paul Absmanner

unread,
Jun 13, 2025, 3:45:05 AM6/13/25
to A gathering place for the Open Rail Data community
Hi Peter, thanks for the fast reply.
Here is a stand alone python script.
TODO:
pip install -r requirements.txt
in minimal_example.py replace USERNAME and PASSWORD with your credentials in order to access ojp.nationalrail.co.uk/webservices
Most importantly is the train operator value, which we need. 
requirements.txt
minimal_example.py
response.xml

Jeskynar

unread,
Jun 13, 2025, 3:53:02 AM6/13/25
to openrail...@googlegroups.com
Hi

Sorry I can't help with your problem specifically but you're not the only one who has used zeep



--
You received this message because you are subscribed to the Google Groups "A gathering place for the Open Rail Data community" group.
To unsubscribe from this group and stop receiving emails from it, send an email to openraildata-t...@googlegroups.com.
To view this discussion, visit https://groups.google.com/d/msgid/openraildata-talk/170ac3be-2881-4253-a970-2ec48e8a2e7dn%40googlegroups.com.

Paul Absmanner

unread,
Jun 13, 2025, 4:54:31 AM6/13/25
to A gathering place for the Open Rail Data community
Thank you Jeskynar for the link.
As it turns out, the error also happens when using the rtjp-client-python repo.

Raw XML:
          <ns2:operator>
            <ns3:code>LM</ns3:code>
            <ns3:name>West Midlands Trains</ns3:name>
          </ns2:operator>


Parsed output:
                    'destinations': [],
                    'destinationInstants': [],
                    'originPlatform': None,
                    'destinationPlatform': None,
                    'realtimeClassification': None,
                    'mode': None,
                    'operator': None,
                    'trainUID': None,
                    'trainRetailID': None,
                    'trainRID': None,
                    'isReplacementBus': None,
                    'temporaryTrain': None,
                    'reservable': None,
                    'seatingClass': None,
                    'cateringCodes': None,
                    'trainCategory': None,
                    'iptisTripIdentifier': None,
                    'timetable': None,

if someone wants to use this repo for reproducing the error:
I used the following settings:
want_from = 'LIV'
want_to   = 'EUS'
want_outward_hour = 11
want_outward_min  = 5
want_inward_hour  = 16
want_fare_class   = 'STANDARD'

python .\rtjp.py  -q 2025-06-14

Paul Absmanner

unread,
Jun 17, 2025, 1:25:59 AM6/17/25
to A gathering place for the Open Rail Data community

Workaround: I used the Zeep HistoryPlugin in order to receive the raw XML from the request. Then i manually parsed the type, using lxml package, into my Zeep reponse. This works pretty good if you only need one or two additional types.

Thank you for your help.

Case closed  😉

Jeskynar

unread,
Jun 17, 2025, 4:53:00 AM6/17/25
to openrail...@googlegroups.com
Thanks for updating us with a solution.

I notice that the rtjp-client-python code debug output contains a '_raw_elements' entry
which is stripped out by the call to remove_keys_recursively( '_raw_elements' )

The content of _raw_elements seems to be the things you are looking for?

                     'leg': [{'_raw_elements': deque([<Element {http://www.thalesgroup.com/ojp/jpdlr}origins at 0x7f70599f5280>,
                                                      <Element {http://www.thalesgroup.com/ojp/jpdlr}destinationInstants at 0x7f70599f5340>,
                                                      <Element {http://www.thalesgroup.com/ojp/jpdlr}destinations at 0x7f70599f5440>,
                                                      <Element {http://www.thalesgroup.com/ojp/jpdlr}realtimeClassification at 0x7f70599f53c0>,
                                                      <Element {http://www.thalesgroup.com/ojp/jpdlr}mode at 0x7f70599f5580>,
                                                      <Element {http://www.thalesgroup.com/ojp/jpdlr}operator at 0x7f70599f55c0>,
                                                      <Element {http://www.thalesgroup.com/ojp/jpdlr}timetable at 0x7f70599f5600>]),


Reply all
Reply to author
Forward
0 new messages