2012-02-22 6 views
12

[Major Edit основанные на опыте, так как 1-й пост два дня назад.]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>[email protected]?clientVersion=7.3</ns3:OriginHost> 
     </ns3:ShUpdate> 
    </ns2:Body> 
</SOAP-ENV:Envelope> 

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

The user data provided did not validate against the MetaSwitch XML Schema for user data.
Details: cvc-enumeration-valid: Value '' is not facet-valid with respect to enumeration '[Controlling, Abandoned, Cautiously controlling]'. It must be a value from the enumeration.

Если взять сгенерированный 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()]) 
+0

вы можете посмотреть по следующим ссылкам: 1> http://stackoverflow.com/questions/2469988/how-to-pass-soap-headers-into-python-suds-that-are-not -defined-in-wsdl-file 2> http://stackoverflow.com/questions/2964867/add-header-section-to-soap-request-using-soappy. – avasal

+0

Я их видел. Они связаны с добавлением заголовков (если я неправильно понимаю/неправильно читаю), а не заставлять Suds включать префиксы для пространств имен, о которых он знает). – Torid

+0

надеясь, что кто-то может добавить более чистый ответ! – trinth

ответ

2

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

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

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

Хорошая попытка, но, к сожалению, это не работает. После прочтения WSDL Suds напрямую не раскрывает XML. Таким образом, код Python никогда не имеет доступа к строковому представлению XML. Вместо этого Suds предоставляет доступ к ряду типов объектов, где код Python может устанавливать значения и т. Д. Затем Suds внутренне генерирует/отправляет XML, когда код Python вызывает метод, определенный в XML. – Torid

+0

Если я правильно читаю документы, текущая версия suds поддерживает плагины. Кажется, что, вызывая класс из MessagePlugin и повторно отклоняя метод 'send()', вы можете проверить/изменить текст до его отправки. Переопределение метода 'receive()' позволяет вам проверять/модифицировать полученный XML до его анализа. В сочетании с регулярным выражением это может сделать трюк. –

+0

Похоже, что есть проблема с отправляющим MessagePLugin, так что, даже если я получаю регулярное выражение правильно (проверено в автономном режиме), когда запрос SOAP попадает на провод, он содержит исходный XML, а не sub'd XML. См. Http://lists.fedoraproject.org/pipermail/suds/2010-November/001240.html. – Torid

15

Вы можете использовать плагин для изменения 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()]) 

Цитируя documentation:

The MessagePlugin currently has (5) hooks ::
(...)
sending()
Provides the plugin with the opportunity to inspect/modify the message text before it is sent.

В основном 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() 
+0

Интересно. Я не знал о MessagePlugins. Итак ... Я протестировал подход, и изменение XML в MessagePlugin меня закрыло, но получение правильного синтаксиса regex является сложным. В частности, он должен оставить самостоятельно при удалении (например) и . Вы, ребята, лучше в регулярном выражении, чем я? – Torid

+0

Я обновил свой ответ, посмотрю, работает ли он на вас. – dusan

+0

Использование marshalled MessagePlugin выглядит многообещающе, но я не могу делать предположения о позиции элементов, подлежащих обрезке. [Есть несколько тысяч указаний на обслуживание, определенных в WSDL, с почти таким же количеством уникальных форматов/полей.] Я попытался сделать tostring() в элементе, содержащем контекст (для ct в context.envelope: ctStr = el.tostring (ct)) после импорта xml.etree.ElementTree как el, но получил ошибку AttributeError: У экземпляра элемента нет атрибута 'getiterator'? – Torid

0

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

By 'walk (f)' вы имеете в виду 'walk (w)'? – dusan

+0

yep - разметка на SO изменила имя этой функции, когда я ее просматривал. Очень запутанно - я отредактирую. –

1

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

вы можете настроить этот код так, чтобы он был готов к использованию в качестве плагина? – radtek

1

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

Использование метода отправки в 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() управляет ими, когда мы итерации и вызываем проблемы.

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

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

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

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

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

6

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

+1

Это путь! Спасибо – radtek

+1

Это красиво и чисто. Если у вас нет контроля над созданием клиента, но у вас есть доступ к нему после этого факта, вы можете установить такой вариант плагина следующим образом: 'client.set_options (plugins = [PrunePlugin()])' –

4

Метод фабрики 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 
} 
Смежные вопросы