Suds генерирует пустые элементы; как их удалить?

[Большая правка на основе опыта, полученного после первого сообщения два дня назад.]

Я создаю скрипт Python SOAP/XML с использованием Suds, но изо всех сил пытаюсь заставить код генерировать SOAP/XML, приемлемый для сервера. Я думал, что проблема в том, что Suds не генерирует префиксы для внутренних элементов, но впоследствии выяснилось, что отсутствие префиксов (см. Sh-Data и внутренние элементы) не является проблемой, поскольку элементы Sh-Data и MetaSwitchData объявляют соответствующие пространства имен (см. ниже).

<SOAP-ENV:Envelope xmlns:ns3="http://www.metaswitch.com/ems/soap/sh" xmlns:ns0="http://www.metaswitch.com/ems/soap/sh/userdata" xmlns:ns1="http://www.metaswitch.com/ems/soap/sh/servicedata" xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns2:Body>
      <ns3:ShUpdate>
         <ns3:UserIdentity>Meribel/TD Test Sub Gateway 3</ns3:UserIdentity>
         <ns3:DataReference>0</ns3:DataReference>
         <ns3:UserData>
            <Sh-Data xmlns="http://www.metaswitch.com/ems/soap/sh/userdata">
               <RepositoryData>
                  <ServiceIndication>Meta_SubG_BaseInformation</ServiceIndication>
                  <SequenceNumber>0</SequenceNumber>
                  <ServiceData>
                     <MetaSwitchData xmlns="http://www.metaswitch.com/ems/soap/sh/servicedata" IgnoreSequenceNumber="False" MetaSwitchVersion="?">
                        <Meta_SubG_BaseInformation Action="apply">
                           <NetworkElementName>Meribel</NetworkElementName>
                           <Description>TD Test Sub Gateway 3</Description>
                           <DomainName>test.datcon.co.uk</DomainName>
                           <MediaGatewayModel>Cisco ATA</MediaGatewayModel>
                           <CallFeatureServerControlStatus/>
                           <CallAgentControlStatus/>
                           <UseStaticNATMapping/>
                           <AuthenticationRequired/>
                           <ProviderStatus/>
                           <DeactivationMode/>
                        </Meta_SubG_BaseInformation>
                     </MetaSwitchData>
                  </ServiceData>
               </RepositoryData>
            </Sh-Data>
         </ns3:UserData>
         <ns3:OriginHost>user@domain.com?clientVersion=7.3</ns3:OriginHost>
      </ns3:ShUpdate>
   </ns2:Body>
</SOAP-ENV:Envelope>

Но это все равно не удается. Проблема в том, что Suds генерирует пустые элементы для необязательных элементов (помеченных как Mandatory = No в WSDL). Но сервер требует, чтобы необязательный элемент либо присутствовал с разумным значением, либо отсутствовал, и я получаю следующую ошибку (потому что элемент не является одним из допустимых значений.

Предоставленные данные пользователя не прошли проверку на соответствие XML-схеме MetaSwitch для данных пользователя.
Детали: cvc-enumeration-valid: Значение '' не является facet-valid в отношении перечисления '[Controlling, Abandoned, Cautiously controlling]'. Это должно быть значение из перечисления.

Если я возьму сгенерированный SOAP/XML в SOAPUI и удалю пустые элементы, запрос работает нормально.

Есть ли способ заставить Suds либо не генерировать пустые элементы для необязательных полей, либо удалить их в коде?

Крупное обновление

Я решил эту проблему (которую я видел в другом месте), но довольно неэлегантным способом. Поэтому я выкладываю свое текущее решение в надежде, что а) оно поможет другим и/или б) кто-то сможет предложить лучший обходной путь.

Оказывается, проблема была не в том, что Suds генерирует пустые элементы для необязательных элементов (отмеченных как Mandatory = No в WSDL). Но скорее в том, что Suds генерирует пустые элементы для необязательных сложных элементов. Например, следующие элементы Meta_SubG_BaseInformation являются простыми элементами и Suds не генерирует для них ничего в SOAP/XML.

<xs:element name="CMTS" type="xs:string" minOccurs="0">
    <xs:annotation>
        <xs:documentation>
            <d:DisplayName firstVersion="5.0" lastVersion="7.4">CMTS</d:DisplayName>
            <d:ValidFrom>5.0</d:ValidFrom>
            <d:ValidTo>7.4</d:ValidTo>
            <d:Type firstVersion="5.0" lastVersion="7.4">String</d:Type>
            <d:BaseAccess firstVersion="5.0" lastVersion="7.4">RWRWRW</d:BaseAccess>
            <d:Mandatory firstVersion="5.0" lastVersion="7.4">No</d:Mandatory>
            <d:MaxLength firstVersion="5.0" lastVersion="7.4">1024</d:MaxLength>
        </xs:documentation>
    </xs:annotation>
</xs:element>

<xs:element name="TAGLocation" type="xs:string" minOccurs="0">
    <xs:annotation>
        <xs:documentation>
            <d:DisplayName>Preferred location of Trunk Gateway</d:DisplayName>
            <d:Type>String</d:Type>
            <d:BaseAccess>RWRWRW</d:BaseAccess>
            <d:Mandatory>No</d:Mandatory>
            <d:DefaultValue>None</d:DefaultValue>
            <d:MaxLength>1024</d:MaxLength>
        </xs:documentation>
    </xs:annotation>
</xs:element>

Напротив, следующий элемент Meta_SubG_BaseInformation является сложным элементом, и даже когда он является необязательным и мой код не присваивает ему значение, он попадает в сгенерированный SOAP/XML.

<xs:element name="ProviderStatus" type="tMeta_SubG_BaseInformation_ProviderStatus" minOccurs="0">
    <xs:annotation>
        <xs:documentation>
            <d:DisplayName>Provider status</d:DisplayName>
            <d:Type>Choice of values</d:Type>
            <d:BaseAccess>R-R-R-</d:BaseAccess>
            <d:Mandatory>No</d:Mandatory>
            <d:Values>
                <d:Value>Unavailable</d:Value>
                <d:Value>Available</d:Value>
                <d:Value>Inactive</d:Value>
                <d:Value>Active</d:Value>
                <d:Value>Out of service</d:Value>
                <d:Value>Quiescing</d:Value>
                <d:Value>Unconfigured</d:Value>
                <d:Value>Pending available</d:Value>
            </d:Values>
        </xs:documentation>
    </xs:annotation>
</xs:element>

Suds генерирует следующее для ProviderStatus, что (как сказано выше) расстраивает мой сервер.

<ProviderStatus/>

Обходным решением является установка всех элементов Meta_SubG_BaseInformation в None после создания родительского элемента и перед присвоением значений, как показано ниже. Это излишне для простых элементов, но гарантирует, что неприсвоенные сложные элементы не приведут к созданию SOAP/XML.

subGatewayBaseInformation = client.factory.create('ns1:Meta_SubG_BaseInformation')
for (el) in subGatewayBaseInformation:
  subGatewayBaseInformation.__setitem__(el[0], None)
subGatewayBaseInformation._Action            = 'apply'
subGatewayBaseInformation.NetworkElementName = 'Meribel'
etc...

Это приводит к тому, что Suds генерирует SOAP/XML без пустых элементов, что приемлемо для моего сервера.

Но знает ли кто-нибудь более чистый способ достижения того же эффекта?

Решение ниже основано на ответах / комментариях dusan и Roland Smith ниже.

Это решение использует Suds MessagePlugin для обрезки "пустого" XML вида перед тем, как Suds отправит запрос по проводам. Обрезка нужна только при ShUpdates (когда мы обновляем данные на сервере), и логика (особенно индексация вниз в дочерние элементы для получения списка элементов индикации сервиса) очень специфична для WSDL. Она не будет работать для других WSDL.

class MyPlugin(MessagePlugin):
  def marshalled(self, context):
    pruned = []
    req = context.envelope.children[1].children[0]
    if (req.name == 'ShUpdate'):
      si = req.children[2].children[0].children[0].children[2].children[0].children[0]
      for el in si.children:
        if re.match('<[a-zA-Z0-9]*/>', Element.plain(el)):
          pruned.append(el)
      for p in pruned:
        si.children.remove(p)

И тогда нам просто нужно ссылаться на плагин, когда мы создаем клиента.

client = Client(url, plugins=[MyPlugin()])
12
задан GSerg 23 April 2014 в 06:50
поделиться