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:
parent
492b2dea45
commit
29f6b5917f
|
@ -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)
|
||||
-------------------------
|
||||
|
||||
|
|
12
TODO.txt
12
TODO.txt
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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("""\
|
||||
|
|
Loading…
Reference in New Issue