fix handling recursive XSD imports

Now XSD import does not attempt to refetch XSD schemas already constructed
during the same XSD load operation.

Updated todo list.
Updated project release notes.
This commit is contained in:
Jurko Gospodnetić 2015-07-01 11:46:19 +02:00
parent 492b2dea45
commit 29f6b5917f
6 changed files with 56 additions and 34 deletions

View File

@ -163,6 +163,7 @@ version 0.7 (development)
providing the initial fix.
* Fixed loading recursive WSDL imports.
* Fixed loading recursive XSD imports/includes.
* Made ``suds`` no longer eat up, log & ignore exceptions raised from registered
user plugins (detected & reported by Ezequiel Ruiz & Bouke Haarsma, patch &
test case contributed by Bouke Haarsma).
@ -415,10 +416,6 @@ version 0.7 (development)
related ``pytest`` ``xdist`` usage problems, discovering & explaining the
underlying issue as well as providing an initial project patch for it.
* Known defects.
* Loading recursive XSD imports/includes is broken.
version 0.6 (2014-01-24)
-------------------------

View File

@ -1501,9 +1501,15 @@ PRIORITIZED:
(30.06.2015.)
* (Jurko) Fix recursive XSD import issue.
* Add tests.
* Fix issue.
(+) * (Jurko) Review and commit unpublished cleanup work on Jurko's machine.
(01.06.2015.)
(+) * (Jurko) Fix recursive XSD import issue.
(+) * Add tests.
(+) * Fix issue.
(02.06.2015.)
* (Jurko) Review and commit unpublished cleanup work on Jurko's machine.

View File

@ -239,16 +239,17 @@ class Definitions(WObject):
def build_schema(self):
"""Process L{Types} objects and create the schema collection."""
loaded_schemata = {}
container = SchemaCollection(self)
for t in (t for t in self.types if t.local()):
for root in t.contents():
schema = Schema(root, self.url, self.options, container)
schema = Schema(root, self.url, self.options, loaded_schemata, container)
container.add(schema)
if not container:
root = Element.buildPath(self.root, "types/schema")
schema = Schema(root, self.url, self.options, container)
schema = Schema(root, self.url, self.options, loaded_schemata, container)
container.add(schema)
self.schema = container.load(self.options)
self.schema = container.load(self.options, loaded_schemata)
#TODO: Recheck this XSD schema merging. XSD schema imports are not
# supposed to be transitive. They only allow the importing schema to
# reference entities from the imported schema, but do not include them

View File

@ -80,7 +80,7 @@ class SchemaCollection(UnicodeMixin):
existing.root.children += schema.root.children
existing.root.nsprefixes.update(schema.root.nsprefixes)
def load(self, options):
def load(self, options, loaded_schemata):
"""
Load schema objects for the root nodes.
- de-reference schemas
@ -88,6 +88,8 @@ class SchemaCollection(UnicodeMixin):
@param options: An options dictionary.
@type options: L{options.Options}
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
@type loaded_schemata: dict
@return: The merged schema.
@rtype: L{Schema}
@ -97,7 +99,7 @@ class SchemaCollection(UnicodeMixin):
for child in self.children:
child.build()
for child in self.children:
child.open_imports(options)
child.open_imports(options, loaded_schemata)
for child in self.children:
child.dereference()
log.debug("loaded:\n%s", self)
@ -199,7 +201,8 @@ class Schema(UnicodeMixin):
Tag = "schema"
def __init__(self, root, baseurl, options, container=None):
def __init__(self, root, baseurl, options, loaded_schemata=None,
container=None):
"""
@param root: The XML root.
@type root: L{sax.element.Element}
@ -207,6 +210,9 @@ class Schema(UnicodeMixin):
@type baseurl: basestring
@param options: An options dictionary.
@type options: L{options.Options}
@param loaded_schemata: An optional already loaded schemata cache (URL
--> Schema).
@type loaded_schemata: dict
@param container: An optional container.
@type container: L{SchemaCollection}
@ -237,6 +243,9 @@ class Schema(UnicodeMixin):
# really necessary or if we can simply build our top-level WSDL
# contained schemata one by one as they are loaded
if container is None:
if loaded_schemata is None:
loaded_schemata = {}
loaded_schemata[baseurl] = self
#TODO: It seems like this build() step can be done for each schema
# on its own instead of letting the container do it. Building our
# XSD schema objects should not require any external schema
@ -249,7 +258,7 @@ class Schema(UnicodeMixin):
# get built, but there is bound to be a cleaner way to do this,
# similar to how we support such XML modifications in suds plugins.
self.build()
self.open_imports(options)
self.open_imports(options, loaded_schemata)
log.debug("built:\n%s", self)
self.dereference()
log.debug("dereferenced:\n%s", self)
@ -326,7 +335,7 @@ class Schema(UnicodeMixin):
schema.merged = True
return self
def open_imports(self, options):
def open_imports(self, options, loaded_schemata):
"""
Instruct all contained L{sxbasic.Import} children to import all of
their referenced schemas. The imported schema contents are I{merged}
@ -334,13 +343,15 @@ class Schema(UnicodeMixin):
@param options: An options dictionary.
@type options: L{options.Options}
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
@type loaded_schemata: dict
"""
for imp in self.imports:
imported = imp.open(options)
imported = imp.open(options, loaded_schemata)
if imported is None:
continue
imported.open_imports(options)
imported.open_imports(options, loaded_schemata)
log.debug("imported:\n%s", imported)
self.merge(imported)
@ -414,7 +425,7 @@ class Schema(UnicodeMixin):
except Exception:
return False
def instance(self, root, baseurl, options):
def instance(self, root, baseurl, loaded_schemata, options):
"""
Create and return an new schema object using the specified I{root} and
I{URL}.
@ -423,6 +434,8 @@ class Schema(UnicodeMixin):
@type root: L{sax.element.Element}
@param baseurl: A base URL.
@type baseurl: str
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
@type loaded_schemata: dict
@param options: An options dictionary.
@type options: L{options.Options}
@return: The newly created schema object.
@ -430,7 +443,7 @@ class Schema(UnicodeMixin):
@note: This is only used by Import children.
"""
return Schema(root, baseurl, options)
return Schema(root, baseurl, options, loaded_schemata)
def str(self, indent=0):
tab = "%*s" % (indent * 3, "")

View File

@ -549,12 +549,14 @@ class Import(SchemaObject):
self.location = self.locations.get(self.ns[1])
self.opened = False
def open(self, options):
def open(self, options, loaded_schemata):
"""
Open and import the referenced schema.
@param options: An options dictionary.
@type options: L{options.Options}
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
@type loaded_schemata: dict
@return: The referenced schema.
@rtype: L{Schema}
@ -569,7 +571,11 @@ class Import(SchemaObject):
if self.location is None:
log.debug("imported schema (%s) not-found", self.ns[1])
else:
result = self.__download(options)
url = self.location
if "://" not in url:
url = urljoin(self.schema.baseurl, url)
result = (loaded_schemata.get(url) or
self.__download(url, loaded_schemata, options))
log.debug("imported:\n%s", result)
return result
@ -578,17 +584,14 @@ class Import(SchemaObject):
if self.ns[1] != self.schema.tns[1]:
return self.schema.locate(self.ns)
def __download(self, options):
def __download(self, url, loaded_schemata, options):
"""Download the schema."""
url = self.location
try:
if "://" not in url:
url = urljoin(self.schema.baseurl, url)
reader = DocumentReader(options)
d = reader.open(url)
root = d.root()
root.set("url", url)
return self.schema.instance(root, url, options)
return self.schema.instance(root, url, loaded_schemata, options)
except TransportError:
msg = "import schema (%s) at (%s), failed" % (self.ns[1], url)
log.error("%s, %s", self.id, msg, exc_info=True)
@ -618,12 +621,14 @@ class Include(SchemaObject):
self.location = self.locations.get(self.ns[1])
self.opened = False
def open(self, options):
def open(self, options, loaded_schemata):
"""
Open and include the referenced schema.
@param options: An options dictionary.
@type options: L{options.Options}
@param loaded_schemata: Already loaded schemata cache (URL --> Schema).
@type loaded_schemata: dict
@return: The referenced schema.
@rtype: L{Schema}
@ -632,22 +637,23 @@ class Include(SchemaObject):
return
self.opened = True
log.debug("%s, including location='%s'", self.id, self.location)
result = self.__download(options)
url = self.location
if "://" not in url:
url = urljoin(self.schema.baseurl, url)
result = (loaded_schemata.get(url) or
self.__download(url, loaded_schemata, options))
log.debug("included:\n%s", result)
return result
def __download(self, options):
def __download(self, url, loaded_schemata, options):
"""Download the schema."""
url = self.location
try:
if "://" not in url:
url = urljoin(self.schema.baseurl, url)
reader = DocumentReader(options)
d = reader.open(url)
root = d.root()
root.set("url", url)
self.__applytns(root)
return self.schema.instance(root, url, options)
return self.schema.instance(root, url, loaded_schemata, options)
except TransportError:
msg = "include schema at (%s), failed" % url
log.error("%s, %s", self.id, msg, exc_info=True)

View File

@ -2030,7 +2030,6 @@ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
("DoesNotExist", "OMG"))
@pytest.mark.xfail
def test_recursive_XSD_import():
url_xsd = "suds://xsd"
xsd = b("""\