python-zeep icon indicating copy to clipboard operation
python-zeep copied to clipboard

UnresolvedType class missing is_global attribute definition

Open lemasson-h opened this issue 5 years ago • 1 comments

Hello,

I have encountered the following issue:

--- Logging error ---
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/logging/__init__.py", line 994, in emit
    msg = self.format(record)
  File "/usr/local/lib/python3.6/logging/__init__.py", line 840, in format
    return fmt.format(record)
  File "get_ga_accounts.py", line 15, in format
    str(current_arg) for current_arg in record.args
  File "get_ga_accounts.py", line 15, in <genexpr>
    str(current_arg) for current_arg in record.args
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/types/complex.py", line 73, in __str__
    return "%s(%s)" % (self.__class__.__name__, self.signature())
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/types/complex.py", line 475, in signature
    part = element.signature(schema, standalone=False)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/elements/indicators.py", line 255, in signature
    value = element.signature(schema, standalone=False)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/elements/element.py", line 309, in signature
    if self.type.is_global or (not standalone and self.is_global):
AttributeError: 'UnresolvedType' object has no attribute 'is_global'
Call stack:
  File "get_ga_accounts.py", line 73, in <module>
    main(adwords_client)
  File "get_ga_accounts.py", line 33, in main
    'ManagedCustomerService', version='v201809')
  File "/usr/local/lib/python3.6/site-packages/googleads/adwords.py", line 315, in GetService
    cache=self.cache)
  File "/usr/local/lib/python3.6/site-packages/googleads/common.py", line 774, in __init__
    endpoint, transport=transport, plugins=plugins)
  File "/usr/local/lib/python3.6/site-packages/zeep/client.py", line 68, in __init__
    self.wsdl = Document(wsdl, self.transport, settings=self.settings)
  File "/usr/local/lib/python3.6/site-packages/zeep/wsdl/wsdl.py", line 82, in __init__
    root_definitions = Definition(self, document, self.location)
  File "/usr/local/lib/python3.6/site-packages/zeep/wsdl/wsdl.py", line 184, in __init__
    self.parse_types(doc)
  File "/usr/local/lib/python3.6/site-packages/zeep/wsdl/wsdl.py", line 316, in parse_types
    self.types.add_documents(schema_nodes, self.location)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/schema.py", line 113, in add_documents
    document = self.create_new_document(node, location)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/schema.py", line 213, in create_new_document
    schema.load(self, node)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/schema.py", line 440, in load
    visitor.visit_schema(node)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/visitor.py", line 160, in visit_schema
    self.process(child, parent=node)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/visitor.py", line 88, in process
    result = visit_func(self, node, parent)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/visitor.py", line 635, in visit_complex_type
    self.register_type(qname, xsd_type)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/visitor.py", line 73, in register_type
    self.document.register_type(qname, instance)
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/schema.py", line 499, in register_type
    self._add_component(qname, value, self._types, "type")
  File "/usr/local/lib/python3.6/site-packages/zeep/xsd/schema.py", line 580, in _add_component
    logger.debug("register_%s(%r, %r)", item_name, name, value)
Message: 'register_%s(%r, %r)'
Arguments: ('type', '{https://adwords.google.com/api/adwords/mcm/v201809}AccountLabel', <zeep.xsd.dynamic_types.AccountLabel object at 0x7fb17908fb70>)

Basically the problem seems to come from 2 things:

  • A logger forces the arguments passed when logging a message to be stringified
  • UnresolvedType class in zeep/xsd/types/unresolved.py isn't calling it's parent init, causing the is_global attribute to not be defined.

The combination of those two, means that this line: logger.debug("register_%s(%r, %r)", item_name, name, value) in zeep/xsd/schema.py, line 580 is calling a logger, which when forcing the arguments to be stringified, is calling the element signature, where it is expecting the type attribute to have the attribute is_global. While if we don't pass through str, but through repr (default behaviour with the default formatter from python), it is fine as it doesn't try to access the is_global attribute on the type.

I have tried by updating UnresolvedType init to call the parent and it resolved the problem.

Would it be possible to find a solution to resolve it, please?

By the way, I am not controlling the logging formatter, I am using a framework called airflow, which has its own logger formatter which is responsible for forcing the arguments to be transformed into string instances.

  1. Versions python 3.6 zeep==3.4.0 googleads==22.0.*

  2. The wsdl https://adwords.google.com/api/adwords/mcm/v201809/ManagedCustomerService?wsdl

  3. Script A simple version, could be something like that:

#!/usr/bin/env python
#
# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""This example gets the account hierarchy under the current account.
The LoadFromStorage method is pulling credentials and properties from a
"googleads.yaml" file. By default, it looks for this file in your home
directory. For more information, see the "Caching authentication information"
section of our README.
"""
import logging
from googleads import adwords

PAGE_SIZE = 500


class FormatterForcingStrMagicMethodInsteadOfReprMagicMethod(logging.Formatter):
    """
    Define a formatter that forces the arguments to be stringify instead of letting the repr
    be used by the logger by default.
    """
    def format(self, record):
        if isinstance(record.args, (tuple, list)):
            record.args = tuple(
                str(current_arg) for current_arg in record.args
            )

        return super().format(record)


logging.getLogger('zeep.xsd.schema').setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = FormatterForcingStrMagicMethodInsteadOfReprMagicMethod(
    '[%(asctime)s] %(levelname)s - %(name)s: %(message)s'
)
handler.setFormatter(formatter)
logging.getLogger('zeep.xsd.schema').addHandler(handler)


def main(client):
    # Initialize appropriate service.
    managed_customer_service = client.GetService(
        'ManagedCustomerService', version='v201809')

    # Construct selector to get all accounts.
    offset = 0
    selector = {
        'fields': ['CustomerId', 'Name'],
        'paging': {
            'startIndex': str(offset),
            'numberResults': str(PAGE_SIZE)
        }
    }
    more_pages = True
    accounts = list()

    while more_pages:
        # Get serviced account graph.
        page = managed_customer_service.get(selector)
        if 'entries' in page and page['entries']:
            # Map from customerID to account.
            for account in page['entries']:
                accounts.append(account)
        offset += PAGE_SIZE
        selector['paging']['startIndex'] = str(offset)
        more_pages = offset < int(page['totalNumEntries'])

    print("Accounts", accounts[0])


if __name__ == '__main__':
    # Initialize client object.
    data = """
adwords:
  #############################################################################
  # Required Fields                                                           #
  #############################################################################
  developer_token: INSERT_DEVELOPER_TOKEN_HERE
  #############################################################################
  # Optional Fields                                                           #
  #############################################################################
  # client_customer_id: INSERT_CLIENT_CUSTOMER_ID_HERE
  # user_agent: INSERT_USER_AGENT_HERE
  # partial_failure: True
  # validate_only: True
  #############################################################################
  # OAuth2 Configuration                                                      #
  # Below you may provide credentials for either the installed application or #
  # service account flows. Remove or comment the lines for the flow you're    #
  # not using.                                                                #
  #############################################################################
  # The following values configure the client for the installed application
  # flow.
  client_id: INSERT_OAUTH_2_CLIENT_ID_HERE
  client_secret: INSERT_CLIENT_SECRET_HERE
  refresh_token: INSERT_REFRESH_TOKEN_HERE
"""
    adwords_client = adwords.AdWordsClient.LoadFromString(data)
    main(adwords_client)

As you will see this depends on the python library from google: https://github.com/googleads/googleads-python-lib and will require to have an account to actually get the credentials to run the script.

lemasson-h avatar Feb 06 '20 16:02 lemasson-h

@lemasson-h Did you find a solution to your problem?

I’m facing same behavior with logging. However my issue has nothing to do with googleads.

alexxxnf avatar Nov 09 '21 09:11 alexxxnf