Suds генерирует пустые элементы;как их убрать? - PullRequest
12 голосов
/ 22 февраля 2012

[Major Edit, основанный на опыте с 1-го поста два дня назад.]

Я создаю сценарий 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).Но сервер требует, чтобы дополнительный элемент либо присутствовал с разумным значением, либо отсутствовал, и я получаю следующую ошибку (поскольку элемент <CallFeatureServerControlStatus/> не является одним из допустимых значений.

Пользовательпредоставленные данные не были проверены в соответствии с XML-схемой MetaSwitch для пользовательских данных.
Подробности: cvc-enumeration-valid: значение '' не является корректным по отношению к перечислению '[Controlling, Abandoned, Осторожно контролирующий]'.быть значением из перечисления.

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

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

Major Update

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

Оказывается, проблема не в том, что Suds генерирует пустые элементы для необязательных элементов (помеченных как Mandatory = No в WSDL).Но вместо этого тот Sud генерирует пустые элементы для необязательных сложных элементов.Например, следующие элементы 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 без пустых элементов, что приемлемона мой сервер.

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

Решение, приведенное ниже, основано на ответах / комментариях от Душана и Ролана Смита ниже.

В этом решении используется SudS MessagePlugin для обрезки «пустого» XML вида <SubscriberType/> до того, как 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()])

Ответы [ 7 ]

15 голосов
/ 06 марта 2012

Вы можете использовать плагин для изменения XML перед отправкой на сервер (мой ответ основан на решении Рональда Смита):

from suds.plugin import MessagePlugin
from suds.client import Client
import re

class MyPlugin(MessagePlugin):
    def sending(self, context):
        context.envelope = re.sub('\s+<.*?/>', '', context.envelope)


client = Client(URL_WSDL, plugins=[MyPlugin()])

Ссылаясь на документацию :

В настоящее время MessagePlugin имеет (5) хуков: *
(...)
send ()
Предоставляет плагину возможность проверять / изменятьтекст сообщения перед отправкой.

Обычно Suds вызывает sending перед отправкой XML, поэтому вы можете изменить сгенерированный XML (содержащийся в context.envelope).Вы должны передать класс плагина MyPlugin в конструктор Client, чтобы это работало.

Редактировать

Другой способ - использовать marshalled для изменения XMLструктура, удаляющая пустые элементы (непроверенный код):

class MyPlugin(MessagePlugin):
    def marshalled(self, context):
        #remove empty tags inside the Body element
        #context.envelope[0] is the SOAP-ENV:Header element
        context.envelope[1].prune()
6 голосов
/ 23 апреля 2014

Есть еще более простой способ - не нужно никаких Reg Ex или захватывающих итераторов;)

Сначала определите плагин:

class PrunePlugin(MessagePlugin):
    def marshalled(self, context):
        context.envelope = context.envelope.prune()

Затем используйте его при создании клиента:

client = Client(url, plugins=[PrunePlugin()])

Метод prune () удалит все пустые узлы, как описано здесь: http://jortel.fedorapeople.org/suds/doc/suds.sax.element.Element-class.html

4 голосов
/ 07 октября 2014

Метод фабрики Suds генерирует обычный объект Python с обычными атрибутами python, которые соответствуют определению типа WSDL.

Вы можете использовать встроенную функцию del для удаления атрибутов.

>>> order_details = c.factory.create('ns2:OrderDetails')
>>> order_details
(OrderDetails){
   Amount = None
   CurrencyCode = None
   OrderChannelType =
      (OrderChannelType){
         value = None
      }
   OrderDeliveryType =
      (OrderDeliveryType){
         value = None
      }
   OrderLines =
      (ArrayOfOrderLine){
         OrderLine[] = <empty>
      }
   OrderNo = None
   TotalOrderValue = None
 }
>>> del order_details.OrderLines
>>> del order_details.OrderDeliveryType
>>> del order_details.OrderChannelType
>>> order_details
(OrderDetails){
   Amount = None
   CurrencyCode = None
   OrderNo = None
   TotalOrderValue = None
 }
2 голосов
/ 05 марта 2012

Вы можете отфильтровать пустые элементы с помощью регулярного выражения.

Предполагается, что ваши XML-данные находятся в строке xmltext;

import re
filteredtext = re.sub('\s+<.*?/>', '', xmltext)
1 голос
/ 21 ноября 2013

Я знаю, что это было закрыто давным-давно, но после личной работы над проблемой мне не хватает текущих ответов.

Использование метода отправки на MessagePlugin не будет работать, потому что, несмотря на то, чтодокументация в значительной степени подразумевает, что вы не можете изменить строку сообщения оттуда.Вы можете получить только конечный результат.

Маршалловый метод, как упоминалось ранее, лучше всего подходит для этого, поскольку он позволяет влиять на XML.Я создал следующий плагин, чтобы исправить проблему для себя:

class ClearEmpty(MessagePlugin):
    def clear_empty_tags(self, tags):
        for tag in tags:
            children = tag.getChildren()[:]
            if children:
                self.clear_empty_tags(children)
            if re.match(r'^<[^>]+?/>$', tag.plain()):
                tag.parent.remove(tag)

    def marshalled(self, context):
        self.clear_empty_tags(context.envelope.getChildren()[:])

Это позволит устранить все пустые теги.Вы можете адаптировать это по мере необходимости, если вам нужно только удалить некоторые пустые теги из некоторого места, но эта рекурсивная функция работает и (если ваша XML-схема не является настолько невыразимо плохой, что ее вложенность превышает глубину вызова Python), не должнавызвать проблему.Обратите внимание, что здесь мы копируем списки, потому что использование remove () приводит к их искажению во время итерации и вызывает проблемы.

На дополнительном примечании регулярное выражение, которое было дано другими ответами, неверно - \s+<.*?/>используется на <test> <thingus/> </test> будет соответствовать <test> <thingus/>, а не просто <thingus/>, как вы могли бы ожидать.Это потому, что > считается «любым символом» ..Если вам действительно нужно использовать regex для решения этой проблемы на визуализированном XML (Примечание: XML - это сложный синтаксис, который лучше обрабатывается лексером), правильный будет следующий: <[^>]*/>.

Мы используем его здесьпотому что я не мог найти самый правильный способ спросить у лексера «является ли это автономным пустым тегом», за исключением проверки вывода этого тега и регулярного выражения против этого.В этом случае я также добавил токены ^ и $, потому что рендеринг тега в этом методе отображает весь его контекст, и это означает, что любой пустой тег под конкретным тегом будет сопоставлен.Мы просто хотим, чтобы был сопоставлен один конкретный тег, чтобы мы могли сказать API удалить его из дерева.

Наконец, чтобы помочь тем, кто ищет то, что могло вызвать этот вопрос, проблема возникладля меня, когда я получил сообщение об ошибке вроде этого:

cvc-enumeration-valid: Value '' is not facet-valid with respect to enumeration

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

1 голос
/ 29 октября 2012

Что вы думаете о следующем MonkeyPatch для пропуска сложных типов со значением None?

from suds.mx.literal import Typed
old_skip = Typed.skip
def new_skip(self, content):
    x = old_skip(self, content)
    if not x and getattr(content.value, 'value', False) is None:
        x = True
    return x
Typed.skip = new_skip
0 голосов
/ 31 мая 2012

Я решил поделиться довольно простым обновлением описанного выше решения, которое должно работать для любого WSDL: Обратите внимание, что метод отправки не является необходимым - он позволяет вам контролировать ваши изменения, поскольку печать запроса отладки Клиента запускает до , когда выполняется метод маршала.

class XMLBS_Plugin(MessagePlugin):
def marshalled(self, context):
    def w(x):
        if x.isempty():
            print "EMPTY: ", x
            x.detach()

    context.envelope.walk(w)

def sending(self,context):
    c = copy.deepcopy(context.envelope)
    c=c.replace('><','>\n<') # some sort of readability
    logging.info("SENDING: \n%s"%c)
...