Подтвердить что ты не робот

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

[Основное правление основано на опыте с 1-го поста два дня назад.]

Я создаю Python SOAP/XML script с помощью 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>[email protected]?clientVersion=7.3</ns3:OriginHost>
      </ns3:ShUpdate>
   </ns2:Body>
</SOAP-ENV:Envelope>

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

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

Если я возьму сгенерированный 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 без пустых элементов, что приемлемо для моего сервера.

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

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

Это решение использует 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()])
4b9b3361

Ответ 1

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

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

import re
filteredtext = re.sub('\s+<.*?/>', '', xmltext)

Ответ 2

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

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()])

Ссылаясь на documentation:

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

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

Edit

Другим способом является использование 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()

Ответ 3

Там еще проще - нет необходимости в каких-либо 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

Метод Suds factory генерирует обычный объект 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
 }

Ответ 5

Что вы думаете о следующем 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

Ответ 6

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

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

Метод marshalled, как упоминалось ранее, лучше всего подходит для этого, так как он позволяет вам влиять на 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/>, как вы могли ожидать. Это связано с тем, что > считается "любым символом" на .. Если вам действительно нужно использовать регулярное выражение для исправления этой проблемы при визуализированном XML (примечание: XML - сложный синтаксис, который лучше обрабатывается лексером), правильным будет <[^>]*/>.

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

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

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

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

Ответ 7

Я подумал, что поделился бы довольно простым обновлением над вышеприведенным решением, которое должно работать для любого 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)