Package pyxb :: Package namespace :: Module archive
[hide private]
[frames] | no frames]

Source Code for Module pyxb.namespace.archive

   1  # -*- coding: utf-8 -*- 
   2  # Copyright 2009-2013, Peter A. Bigot 
   3  # 
   4  # Licensed under the Apache License, Version 2.0 (the "License"); you may 
   5  # not use this file except in compliance with the License. You may obtain a 
   6  # copy of the License at: 
   7  # 
   8  #            http://www.apache.org/licenses/LICENSE-2.0 
   9  # 
  10  # Unless required by applicable law or agreed to in writing, software 
  11  # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
  12  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
  13  # License for the specific language governing permissions and limitations 
  14  # under the License. 
  15   
  16  """Classes and global objects related to archiving U{XML 
  17  Namespaces<http://www.w3.org/TR/2006/REC-xml-names-20060816/index.html>}.""" 
  18   
  19  import pyxb 
  20  import os 
  21  import os.path 
  22  import pyxb.utils.utility 
  23  import logging 
  24   
  25  _log = logging.getLogger(__name__) 
  26   
  27  PathEnvironmentVariable = 'PYXB_ARCHIVE_PATH' 
  28  """Environment variable from which default path to pre-loaded namespaces is 
  29  read.  The value should be a colon-separated list of absolute paths.  The 
  30  character C{&} at the start of a member of the list is replaced by the path to 
  31  the directory where the %{pyxb} modules are found, including a trailing C{/}. 
  32  For example, use C{&pyxb/bundles//} to enable search of any archive bundled 
  33  with PyXB. 
  34   
  35  @note: If you put a path separator between C{&} and the following path, this 
  36  will cause the substitution to be ignored.""" 
  37   
  38  DefaultArchivePrefix = os.path.realpath(os.path.join(os.path.dirname( __file__), '../..')) 
  39  """The default archive prefix, substituted for C{&} in C{PYXB_ARCHIVE_PATH}.""" 
40 41 -def GetArchivePath ():
42 """Return the archive path as defined by the L{PathEnvironmentVariable}, 43 or C{None} if that variable is not defined.""" 44 return os.environ.get(PathEnvironmentVariable)
45 46 # Stuff required for pickling 47 import cPickle as pickle 48 import re
49 50 -class NamespaceArchive (object):
51 """Represent a file from which one or more namespaces can be read, or to 52 which they will be written.""" 53 54 # A code used to identify the format of the archive, so we don't 55 # mis-interpret its contents. 56 # YYYYMMDDHHMM 57 __PickleFormat = '200907190858' 58 59 @classmethod
60 - def _AnonymousCategory (cls):
61 """The category name to use when storing references to anonymous type 62 definitions. For example, attribute definitions defined within an 63 attribute use in a model group definition.that can be referenced frojm 64 ax different namespace.""" 65 return cls.__AnonymousCategory
66 __AnonymousCategory = '_anonymousTypeDefinition' 67 68 @classmethod
69 - def PicklingArchive (cls):
70 """Return a reference to a set specifying the namespace instances that 71 are being archived. 72 73 This is needed to determine whether a component must be serialized as 74 aa reference.""" 75 # NB: Use root class explicitly. If we use cls, when this is invoked 76 # by subclasses it gets mangled using the subclass name so the one 77 # defined in this class is not found 78 return NamespaceArchive.__PicklingArchive
79 # Class variable recording the namespace that is currently being 80 # pickled. Used to prevent storing components that belong to 81 # other namespaces. Should be None unless within an invocation of 82 # SaveToFile. 83 __PicklingArchive = None 84 85 __NamespaceArchives = None 86 """A mapping from generation UID to NamespaceArchive instances.""" 87
88 - def discard (self):
89 """Remove this archive from the set of available archives. 90 91 This is invoked when an archive contains a namespace that the user has 92 specified should not be loaded.""" 93 del self.__NamespaceArchives[self.generationUID()] 94 for ns in self.__namespaces: 95 ns._removeArchive(self)
96 97 @classmethod
98 - def __GetArchiveInstance (cls, archive_file, stage=None):
99 """Return a L{NamespaceArchive} instance associated with the given file. 100 101 To the extent possible, the same file accessed through different paths 102 returns the same L{NamespaceArchive} instance. 103 """ 104 105 nsa = NamespaceArchive(archive_path=archive_file, stage=cls._STAGE_uid) 106 rv = cls.__NamespaceArchives.get(nsa.generationUID(), nsa) 107 if rv == nsa: 108 cls.__NamespaceArchives[rv.generationUID()] = rv 109 rv._readToStage(stage) 110 return rv
111 112 __ArchivePattern_re = re.compile('\.wxs$') 113 114 @classmethod
115 - def PreLoadArchives (cls, archive_path=None, reset=False):
116 """Scan for available archives, associating them with namespaces. 117 118 This only validates potential archive contents; it does not load 119 namespace data from the archives. 120 121 @keyword archive_path: A list of files or directories in which 122 namespace archives can be found. The entries are separated by 123 os.pathsep, which is a colon on POSIX platforms and a semi-colon on 124 Windows. See L{PathEnvironmentVariable}. Defaults to 125 L{GetArchivePath()}. If not defaulted, C{reset} will be forced to 126 C{True}. For any directory in the path, all files ending with 127 C{.wxs} are examined. 128 129 @keyword reset: If C{False} (default), the most recently read set of 130 archives is returned; if C{True}, the archive path is re-scanned and the 131 namespace associations validated. 132 """ 133 134 from pyxb.namespace import builtin 135 136 reset = reset or (archive_path is not None) or (cls.__NamespaceArchives is None) 137 if reset: 138 # Get a list of pre-existing archives, initializing the map if 139 # this is the first time through. 140 if cls.__NamespaceArchives is None: 141 cls.__NamespaceArchives = { } 142 existing_archives = set(cls.__NamespaceArchives.itervalues()) 143 archive_set = set() 144 145 # Ensure we have an archive path. If not, don't do anything. 146 if archive_path is None: 147 archive_path = GetArchivePath() 148 if archive_path is not None: 149 150 # Get archive instances for everything in the archive path 151 candidate_files = pyxb.utils.utility.GetMatchingFiles(archive_path, cls.__ArchivePattern_re, 152 default_path_wildcard='+', default_path=GetArchivePath(), 153 prefix_pattern='&', prefix_substituend=DefaultArchivePrefix) 154 for afn in candidate_files: 155 try: 156 nsa = cls.__GetArchiveInstance(afn, stage=cls._STAGE_readModules) 157 archive_set.add(nsa) 158 except pickle.UnpicklingError: 159 _log.exception('Cannot unpickle archive %s', afn) 160 except pyxb.NamespaceArchiveError: 161 _log.exception('Cannot process archive %s', afn) 162 163 # Do this for two reasons: first, to get an iterable that won't 164 # cause problems when we remove unresolvable archives from 165 # archive_set; and second to aid with forced dependency inversion 166 # testing 167 ordered_archives = sorted(list(archive_set), key=lambda _a: _a.archivePath()) 168 ordered_archives.reverse() 169 170 # Create a graph that identifies dependencies between the archives 171 archive_map = { } 172 for a in archive_set: 173 archive_map[a.generationUID()] = a 174 archive_graph = pyxb.utils.utility.Graph() 175 for a in ordered_archives: 176 prereqs = a._unsatisfiedModulePrerequisites() 177 if 0 < len(prereqs): 178 for p in prereqs: 179 if builtin.BuiltInObjectUID == p: 180 continue 181 da = archive_map.get(p) 182 if da is None: 183 _log.warning('%s depends on unavailable archive %s', a, p) 184 archive_set.remove(a) 185 else: 186 archive_graph.addEdge(a, da) 187 else: 188 archive_graph.addRoot(a) 189 190 # Verify that there are no dependency loops. 191 archive_scc = archive_graph.sccOrder() 192 for scc in archive_scc: 193 if 1 < len(scc): 194 raise pyxb.LogicError("Cycle in archive dependencies. How'd you do that?\n " + "\n ".join([ _a.archivePath() for _a in scc ])) 195 archive = scc[0] 196 if not (archive in archive_set): 197 archive.discard() 198 existing_archives.remove(archive) 199 continue 200 #archive._readToStage(cls._STAGE_COMPLETE) 201 202 # Discard any archives that we used to know about but now aren't 203 # supposed to. @todo make this friendlier in the case of archives 204 # we've already incorporated. 205 for archive in existing_archives.difference(archive_set): 206 _log.info('Discarding excluded archive %s', archive) 207 archive.discard()
208
209 - def archivePath (self):
210 """Path to the file in which this namespace archive is stored.""" 211 return self.__archivePath
212 __archivePath = None 213
214 - def generationUID (self):
215 """The unique identifier for the generation that produced this archive.""" 216 return self.__generationUID
217 __generationUID = None 218
219 - def isLoadable (self):
220 """Return C{True} iff it is permissible to load the archive. 221 Archives created for output cannot be loaded.""" 222 return self.__isLoadable
223 __isLoadable = None 224
225 - def __locateModuleRecords (self):
226 self.__moduleRecords = set() 227 namespaces = set() 228 for ns in pyxb.namespace.utility.AvailableNamespaces(): 229 # @todo allow these; right now it's usually the XML 230 # namespace and we're not prepared to reconcile 231 # redefinitions of those components. 232 if ns.isUndeclaredNamespace(): 233 continue 234 mr = ns.lookupModuleRecordByUID(self.generationUID()) 235 if mr is not None: 236 namespaces.add(ns) 237 mr.prepareForArchive(self) 238 self.__moduleRecords.add(mr) 239 self.__namespaces.update(namespaces)
240 - def moduleRecords (self):
241 """Return the set of L{module records <ModuleRecord>} stored in this 242 archive. 243 244 Each module record represents""" 245 return self.__moduleRecords
246 __moduleRecords = None 247 248 @classmethod
249 - def ForPath (cls, archive_file):
250 """Return the L{NamespaceArchive} instance that can be found at the 251 given path.""" 252 return cls.__GetArchiveInstance(archive_file)
253 254 # States in the finite automaton that is used to read archive contents. 255 _STAGE_UNOPENED = 0 # Haven't even checked for existence 256 _STAGE_uid = 1 # Verified archive exists, obtained generation UID from it 257 _STAGE_readModules = 2 # Read module records from archive, which includes UID dependences 258 _STAGE_validateModules = 3 # Verified pre-requisites for module loading 259 _STAGE_readComponents = 4 # Extracted components from archive and integrated into namespaces 260 _STAGE_COMPLETE = _STAGE_readComponents 261
262 - def _stage (self):
263 return self.__stage
264 __stage = None 265
266 - def __init__ (self, archive_path=None, generation_uid=None, loadable=True, stage=None):
267 """Create a new namespace archive. 268 269 If C{namespaces} is given, this is an output archive. 270 271 If C{namespaces} is absent, this is an input archive. 272 273 @raise IOError: error attempting to read the archive file 274 @raise pickle.UnpicklingError: something is wrong with the format of the library 275 """ 276 self.__namespaces = set() 277 if generation_uid is not None: 278 if archive_path: 279 raise pyxb.LogicError('NamespaceArchive: cannot define both namespaces and archive_path') 280 self.__generationUID = generation_uid 281 self.__locateModuleRecords() 282 elif archive_path is not None: 283 if generation_uid is not None: 284 raise pyxb.LogicError('NamespaceArchive: cannot provide generation_uid with archive_path') 285 self.__archivePath = archive_path 286 self.__stage = self._STAGE_UNOPENED 287 self.__isLoadable = loadable 288 if self.__isLoadable: 289 if stage is None: 290 stage = self._STAGE_moduleRecords 291 self._readToStage(stage) 292 else: 293 pass
294
295 - def add (self, namespace):
296 """Add the given namespace to the set that is to be stored in this archive.""" 297 if namespace.isAbsentNamespace(): 298 raise pyxb.NamespaceArchiveError('Cannot archive absent namespace') 299 self.__namespaces.add(namespace)
300
301 - def update (self, namespace_set):
302 """Add the given namespaces to the set that is to be stored in this archive.""" 303 [ self.add(_ns) for _ns in namespace_set ]
304
305 - def namespaces (self):
306 """Set of namespaces that can be read from this archive.""" 307 return self.__namespaces
308 __namespaces = None 309
310 - def __createPickler (self, output):
311 if isinstance(output, basestring): 312 output = open(output, 'wb') 313 pickler = pickle.Pickler(output, -1) 314 315 # The format of the archive 316 pickler.dump(NamespaceArchive.__PickleFormat) 317 318 # The UID for the set 319 assert self.generationUID() is not None 320 pickler.dump(self.generationUID()) 321 322 return pickler
323
324 - def __createUnpickler (self):
325 unpickler = pickle.Unpickler(open(self.__archivePath, 'rb')) 326 327 fmt = unpickler.load() 328 if self.__PickleFormat != fmt: 329 raise pyxb.NamespaceArchiveError('Archive format is %s, require %s' % (fmt, self.__PickleFormat)) 330 331 self.__generationUID = unpickler.load() 332 333 return unpickler
334
335 - def __readModules (self, unpickler):
336 mrs = unpickler.load() 337 assert isinstance(mrs, set), 'Expected set got %s from %s' % (type(mrs), self.archivePath()) 338 if self.__moduleRecords is None: 339 for mr in mrs.copy(): 340 mr2 = mr.namespace().lookupModuleRecordByUID(mr.generationUID()) 341 if mr2 is not None: 342 mr2._setFromOther(mr, self) 343 mrs.remove(mr) 344 self.__moduleRecords = set() 345 assert 0 == len(self.__namespaces) 346 for mr in mrs: 347 mr._setArchive(self) 348 ns = mr.namespace() 349 ns.addModuleRecord(mr) 350 self.__namespaces.add(ns) 351 self.__moduleRecords.add(mr) 352 else: 353 # Verify the archive still has what was in it when we created this. 354 for mr in mrs: 355 mr2 = mr.namespace().lookupModuleRecordByUID(mr.generationUID()) 356 if not (mr2 in self.__moduleRecords): 357 raise pyxb.NamespaceArchiveError('Lost module record %s %s from %s' % (mr.namespace(), mr.generationUID(), self.archivePath()))
358
360 prereq_uids = set() 361 for mr in self.__moduleRecords: 362 prereq_uids.update(mr.dependsOnExternal()) 363 return prereq_uids
364
365 - def __validatePrerequisites (self, stage):
366 from pyxb.namespace import builtin 367 prereq_uids = self._unsatisfiedModulePrerequisites() 368 for uid in prereq_uids: 369 if builtin.BuiltInObjectUID == uid: 370 continue 371 depends_on = self.__NamespaceArchives.get(uid) 372 if depends_on is None: 373 raise pyxb.NamespaceArchiveError('%s: archive depends on unavailable archive %s' % (self.archivePath(), uid)) 374 depends_on._readToStage(stage)
375
376 - def __validateModules (self):
377 self.__validatePrerequisites(self._STAGE_validateModules) 378 for mr in self.__moduleRecords: 379 ns = mr.namespace() 380 for base_uid in mr.dependsOnExternal(): 381 xmr = ns.lookupModuleRecordByUID(base_uid) 382 if xmr is None: 383 raise pyxb.NamespaceArchiveError('Module %s depends on external module %s, not available in archive path' % (mr.generationUID(), base_uid)) 384 if not xmr.isIncorporated(): 385 _log.info('Need to incorporate data from %s', xmr) 386 else: 387 _log.info('Have required base data %s', xmr) 388 389 for origin in mr.origins(): 390 for (cat, names) in origin.categoryMembers().iteritems(): 391 if not (cat in ns.categories()): 392 continue 393 cross_objects = names.intersection(ns.categoryMap(cat).iterkeys()) 394 if 0 < len(cross_objects): 395 raise pyxb.NamespaceArchiveError('Archive %s namespace %s module %s origin %s archive/active conflict on category %s: %s' % (self.__archivePath, ns, mr, origin, cat, " ".join(cross_objects))) 396 _log.info('%s no conflicts on %d names', cat, len(names))
397
398 - def __readComponentSet (self, unpickler):
399 self.__validatePrerequisites(self._STAGE_readComponents) 400 for n in range(len(self.__moduleRecords)): 401 ns = unpickler.load() 402 mr = ns.lookupModuleRecordByUID(self.generationUID()) 403 assert mr in self.__moduleRecords 404 assert not mr.isIncorporated() 405 objects = unpickler.load() 406 mr._loadCategoryObjects(objects)
407 408 __unpickler = None
409 - def _readToStage (self, stage):
410 if self.__stage is None: 411 raise pyxb.NamespaceArchiveError('Attempt to read from invalid archive %s' % (self,)) 412 try: 413 while self.__stage < stage: 414 if self.__stage < self._STAGE_uid: 415 self.__unpickler = self.__createUnpickler() 416 self.__stage = self._STAGE_uid 417 continue 418 if self.__stage < self._STAGE_readModules: 419 assert self.__unpickler is not None 420 self.__readModules(self.__unpickler) 421 self.__stage = self._STAGE_readModules 422 continue 423 if self.__stage < self._STAGE_validateModules: 424 self.__validateModules() 425 self.__stage = self._STAGE_validateModules 426 continue 427 if self.__stage < self._STAGE_readComponents: 428 assert self.__unpickler is not None 429 self.__stage = self._STAGE_readComponents 430 self.__readComponentSet(self.__unpickler) 431 self.__unpickler = None 432 continue 433 raise pyxb.LogicError('Too many stages (at %s, want %s)' % (self.__stage, stage)) 434 except: 435 self.__stage = None 436 self.__unpickler = None 437 raise
438
439 - def readNamespaces (self):
440 """Read all the components from this archive, integrating them into 441 their respective namespaces.""" 442 self._readToStage(self._STAGE_COMPLETE)
443
444 - def writeNamespaces (self, output):
445 """Store the namespaces into the archive. 446 447 @param output: An instance substitutable for a writable file, or the 448 name of a file to write to. 449 """ 450 import sys 451 452 assert NamespaceArchive.__PicklingArchive is None 453 NamespaceArchive.__PicklingArchive = self 454 assert self.__moduleRecords is not None 455 456 # Recalculate the record/object associations: we didn't assign 457 # anonymous names to the indeterminate scope objects because they 458 # weren't needed for bindings, but they are needed in the archive. 459 for mr in self.__moduleRecords: 460 mr.namespace()._associateOrigins(mr) 461 462 try: 463 # See http://bugs.python.org/issue3338 464 recursion_limit = sys.getrecursionlimit() 465 sys.setrecursionlimit(10 * recursion_limit) 466 467 pickler = self.__createPickler(output) 468 469 assert isinstance(self.__moduleRecords, set) 470 pickler.dump(self.__moduleRecords) 471 472 for mr in self.__moduleRecords: 473 pickler.dump(mr.namespace()) 474 pickler.dump(mr.categoryObjects()) 475 finally: 476 sys.setrecursionlimit(recursion_limit) 477 NamespaceArchive.__PicklingArchive = None
478
479 - def __str__ (self):
480 archive_path = self.__archivePath 481 if archive_path is None: 482 archive_path = '??' 483 return 'NSArchive@%s' % (archive_path,)
484
485 -class _ArchivableObject_mixin (pyxb.cscRoot):
486 """Mix-in to any object that can be stored in a namespace within an archive.""" 487 488 # Need to set this per category item 489 __objectOrigin = None
490 - def _objectOrigin (self):
491 return self.__objectOrigin
492 - def _setObjectOrigin (self, object_origin, override=False):
493 if (self.__objectOrigin is not None) and (not override): 494 if self.__objectOrigin != object_origin: 495 raise pyxb.LogicError('Inconsistent origins for object %s: %s %s' % (self, self.__objectOrigin, object_origin)) 496 else: 497 self.__objectOrigin = object_origin
498
499 - def _prepareForArchive (self, archive):
500 #assert self.__objectOrigin is not None 501 if self._objectOrigin() is not None: 502 return getattr(super(_ArchivableObject_mixin, self), '_prepareForArchive_csc', lambda *_args,**_kw: self)(self._objectOrigin().moduleRecord()) 503 assert not isinstance(self, pyxb.xmlschema.structures._NamedComponent_mixin)
504
505 - def _updateFromOther_csc (self, other):
506 return getattr(super(_ArchivableObject_mixin, self), '_updateFromOther_csc', lambda *_args,**_kw: self)(other)
507
508 - def _updateFromOther (self, other):
509 """Update this instance with additional information provided by the other instance. 510 511 This is used, for example, when a built-in type is already registered 512 in the namespace, but we've processed the corresponding schema and 513 have obtained more details.""" 514 assert self != other 515 return self._updateFromOther_csc(other)
516
517 - def _allowUpdateFromOther (self, other):
518 from pyxb.namespace import builtin 519 assert self._objectOrigin() 520 return builtin.BuiltInObjectUID == self._objectOrigin().generationUID()
521
522 -class _NamespaceArchivable_mixin (pyxb.cscRoot):
523 """Encapsulate the operations and data relevant to archiving namespaces. 524 525 This class mixes-in to L{pyxb.namespace.Namespace}""" 526
527 - def _reset (self):
528 """CSC extension to reset fields of a Namespace. 529 530 This one handles category-related data.""" 531 getattr(super(_NamespaceArchivable_mixin, self), '_reset', lambda *args, **kw: None)() 532 self.__loadedFromArchive = None 533 self.__wroteToArchive = None 534 self.__active = False 535 self.__moduleRecordMap = {}
536
537 - def _loadedFromArchive (self):
538 return self.__loadedFromArchive
539 540 __wroteToArchive = None 541 __loadedFromArchive = None 542
543 - def isActive (self, empty_inactive=False):
544 if self.__isActive and empty_inactive: 545 for (ct, cm) in self._categoryMap().iteritems(): 546 if 0 < len(cm): 547 return True 548 return False 549 return self.__isActive
550
551 - def _activate (self):
552 self.__isActive = True
553 __isActive = None 554
555 - def __init__ (self, *args, **kw):
556 super(_NamespaceArchivable_mixin, self).__init__(*args, **kw)
557
558 - def _setLoadedFromArchive (self, archive):
559 self.__loadedFromArchive = archive 560 self._activate()
561 - def _setWroteToArchive (self, archive):
563
564 - def _removeArchive (self, archive):
565 # Yes, I do want this to raise KeyError if the archive is not present 566 mr = self.__moduleRecordMap[archive.generationUID()] 567 assert not mr.isIncorporated(), 'Removing archive %s after incorporation' % (archive.archivePath(),) 568 del self.__moduleRecordMap[archive.generationUID()]
569
570 - def isLoadable (self):
571 """Return C{True} iff the component model for this namespace can be 572 loaded from a namespace archive.""" 573 for mr in self.moduleRecords(): 574 if mr.isLoadable(): 575 return True 576 return False
577
578 - def isImportAugmentable (self):
579 """Return C{True} iff the component model for this namespace may be 580 extended by import directives. 581 582 This is the case if the namespace has been marked with 583 L{setImportAugmentable}, or if there is no archive or built-in that 584 provides a component model for the namespace.""" 585 if self.__isImportAugmentable: 586 return True 587 for mr in self.moduleRecords(): 588 if mr.isLoadable() or mr.isIncorporated(): 589 return False 590 return True
591 - def setImportAugmentable (self, value=True):
593 __isImportAugmentable = False 594
595 - def loadableFrom (self):
596 """Return the list of archives from which components for this 597 namespace can be loaded.""" 598 rv = [] 599 for mr in self.moduleRecords(): 600 if mr.isLoadable(): 601 rv.append(mr.archive()) 602 return rv
603
604 - def moduleRecords (self):
605 return self.__moduleRecordMap.values()
606 __moduleRecordMap = None 607
608 - def addModuleRecord (self, module_record):
609 assert isinstance(module_record, ModuleRecord) 610 assert not (module_record.generationUID() in self.__moduleRecordMap) 611 self.__moduleRecordMap[module_record.generationUID()] = module_record 612 return module_record
613 - def lookupModuleRecordByUID (self, generation_uid, create_if_missing=False, *args, **kw):
614 rv = self.__moduleRecordMap.get(generation_uid) 615 if (rv is None) and create_if_missing: 616 rv = self.addModuleRecord(ModuleRecord(self, generation_uid, *args, **kw)) 617 return rv
618
619 - def _setState_csc (self, kw):
620 #assert not self.__isActive, 'ERROR: State set for active namespace %s' % (self,) 621 return getattr(super(_NamespaceArchivable_mixin, self), '_getState_csc', lambda _kw: _kw)(kw)
622
623 - def markNotLoadable (self):
624 """Prevent loading this namespace from an archive. 625 626 This marks all archives in which the namespace appears, whether 627 publically or privately, as not loadable.""" 628 if self._loadedFromArchive(): 629 raise pyxb.NamespaceError(self, 'cannot mark not loadable when already loaded') 630 for mr in self.moduleRecords(): 631 mr._setIsLoadable(False)
632
633 -class ModuleRecord (pyxb.utils.utility.PrivateTransient_mixin):
634 __PrivateTransient = set() 635
636 - def namespace (self):
637 return self.__namespace
638 __namespace = None 639
640 - def archive (self):
641 return self.__archive
642 - def _setArchive (self, archive):
643 self.__archive = archive 644 return self
645 __archive = None 646 __PrivateTransient.add('archive') 647
648 - def isPublic (self):
649 return self.__isPublic
650 - def _setIsPublic (self, is_public):
651 self.__isPublic = is_public 652 return self
653 __isPublic = None 654
655 - def isIncorporated (self):
656 return self.__isIncorporated or (self.archive() is None)
657 - def markIncorporated (self):
658 assert self.__isLoadable 659 self.__isIncorporated = True 660 self.__isLoadable = False 661 return self
662 __isIncorporated = None 663 __PrivateTransient.add('isIncorporated') 664
665 - def isLoadable (self):
666 return self.__isLoadable and (self.archive() is not None)
667 - def _setIsLoadable (self, is_loadable):
668 self.__isLoadable = is_loadable 669 return self
670 __isLoadable = None 671
672 - def generationUID (self):
673 return self.__generationUID
674 __generationUID = None 675
676 - def origins (self):
677 return self.__originMap.values()
678 - def addOrigin (self, origin):
679 assert isinstance(origin, _ObjectOrigin) 680 assert not (origin.signature() in self.__originMap) 681 self.__originMap[origin.signature()] = origin 682 return origin
683 - def lookupOriginBySignature (self, signature):
684 return self.__originMap.get(signature)
685 - def _setOrigins (self, origins):
686 if self.__originMap is None: 687 self.__originMap = {} 688 else: 689 self.__originMap.clear() 690 [ self.addOrigin(_o) for _o in origins ] 691 return self
692 __originMap = None 693
694 - def hasMatchingOrigin (self, **kw):
695 for origin in self.origins(): 696 if origin.match(**kw): 697 return True 698 return False
699
700 - def modulePath (self):
701 return self.__modulePath
702 - def setModulePath (self, module_path):
703 assert (module_path is None) or isinstance(module_path, basestring) 704 self.__modulePath = module_path 705 return self
706 __modulePath = None 707
708 - def referencedNamespaces (self):
709 return self.__referencedNamespaces
710 - def _setReferencedNamespaces (self, referenced_namespaces):
711 self.__referencedNamespaces.update(referenced_namespaces) 712 return self
713 - def referenceNamespace (self, namespace):
716 __referencedNamespaces = None 717 718 __constructedLocally = False 719 __PrivateTransient.add('constructedLocally') 720
721 - def __init__ (self, namespace, generation_uid, **kw):
722 from pyxb.namespace import builtin 723 724 super(ModuleRecord, self).__init__() 725 self.__namespace = namespace 726 assert (generation_uid != builtin.BuiltInObjectUID) or namespace.isBuiltinNamespace() 727 self.__isPublic = kw.get('is_public', False) 728 self.__isIncorporated = kw.get('is_incorporated', False) 729 self.__isLoadable = kw.get('is_loadable', True) 730 assert isinstance(generation_uid, pyxb.utils.utility.UniqueIdentifier) 731 self.__generationUID = generation_uid 732 self.__modulePath = kw.get('module_path') 733 self.__originMap = {} 734 self.__referencedNamespaces = set() 735 self.__categoryObjects = { } 736 self.__constructedLocally = True 737 self.__dependsOnExternal = set()
738
739 - def _setFromOther (self, other, archive):
740 if (not self.__constructedLocally) or other.__constructedLocally: 741 raise pyxb.ImplementationError('Module record update requires local to be updated from archive') 742 assert self.__generationUID == other.__generationUID 743 assert self.__archive is None 744 self.__isPublic = other.__isPublic 745 assert not self.__isIncorporated 746 self.__isLoadable = other.__isLoadable 747 self.__modulePath = other.__modulePath 748 self.__originMap.update(other.__originMap) 749 self.__referencedNamespaces.update(other.__referencedNamespaces) 750 if not (other.__categoryObjects is None): 751 self.__categoryObjects.update(other.__categoryObjects) 752 self.__dependsOnExternal.update(other.__dependsOnExternal) 753 self._setArchive(archive)
754
755 - def categoryObjects (self):
756 return self.__categoryObjects
757 - def resetCategoryObjects (self):
758 self.__categoryObjects.clear() 759 for origin in self.origins(): 760 origin.resetCategoryMembers()
761 - def _addCategoryObject (self, category, name, obj):
762 obj._prepareForArchive(self) 763 self.__categoryObjects.setdefault(category, {})[name] = obj
764 - def _loadCategoryObjects (self, category_objects):
765 assert self.__categoryObjects is None 766 assert not self.__constructedLocally 767 ns = self.namespace() 768 ns.configureCategories(category_objects.iterkeys()) 769 for (cat, obj_map) in category_objects.iteritems(): 770 current_map = ns.categoryMap(cat) 771 for (local_name, component) in obj_map.iteritems(): 772 existing_component = current_map.get(local_name) 773 if existing_component is None: 774 current_map[local_name] = component 775 elif existing_component._allowUpdateFromOther(component): 776 existing_component._updateFromOther(component) 777 else: 778 raise pyxb.NamespaceError(self, 'Load attempted to override %s %s in %s' % (cat, local_name, self.namespace())) 779 self.markIncorporated()
780 __categoryObjects = None 781 __PrivateTransient.add('categoryObjects') 782
783 - def dependsOnExternal (self):
784 return self.__dependsOnExternal
785 __dependsOnExternal = None 786
787 - def prepareForArchive (self, archive):
788 assert self.archive() is None 789 self._setArchive(archive) 790 ns = self.namespace() 791 self.__dependsOnExternal.clear() 792 for mr in ns.moduleRecords(): 793 if mr != self: 794 _log.info('This gen depends on %s', mr) 795 self.__dependsOnExternal.add(mr.generationUID()) 796 for obj in ns._namedObjects().union(ns.components()): 797 if isinstance(obj, _ArchivableObject_mixin): 798 if obj._objectOrigin(): 799 obj._prepareForArchive(self)
800 804
805 - def __str__ (self):
806 return 'MR[%s]@%s' % (self.generationUID(), self.namespace())
807
808 -class _ObjectOrigin (pyxb.utils.utility.PrivateTransient_mixin, pyxb.cscRoot):
809 """Marker class for objects that can serve as an origin for an object in a 810 namespace.""" 811 __PrivateTransient = set() 812
813 - def signature (self):
814 return self.__signature
815 __signature = None 816
817 - def moduleRecord (self):
818 return self.__moduleRecord
819 __moduleRecord = None 820
821 - def namespace (self):
822 return self.moduleRecord().namespace()
823
824 - def generationUID (self):
825 return self.moduleRecord().generationUID()
826
827 - def __init__ (self, namespace, generation_uid, **kw):
828 self.__signature = kw.pop('signature', None) 829 super(_ObjectOrigin, self).__init__(**kw) 830 self.__moduleRecord = namespace.lookupModuleRecordByUID(generation_uid, create_if_missing=True, **kw) 831 self.__moduleRecord.addOrigin(self) 832 self.__categoryMembers = { } 833 self.__categoryObjectMap = { }
834
835 - def resetCategoryMembers (self):
836 self.__categoryMembers.clear() 837 self.__categoryObjectMap.clear() 838 self.__originatedComponents = None
839 - def addCategoryMember (self, category, name, obj):
840 self.__categoryMembers.setdefault(category, set()).add(name) 841 self.__categoryObjectMap.setdefault(category, {})[name] = obj 842 self.__moduleRecord._addCategoryObject(category, name, obj)
843 - def categoryMembers (self):
844 return self.__categoryMembers
845 - def originatedObjects (self):
846 if self.__originatedObjects is None: 847 components = set() 848 [ components.update(_v.itervalues()) for _v in self.__categoryObjectMap.itervalues() ] 849 self.__originatedObjects = frozenset(components) 850 return self.__originatedObjects
851 852 # The set of category names associated with objects. Don't throw this 853 # away and use categoryObjectMap.keys() instead: that's transient, and we 854 # need this to have a value when read from an archive. 855 __categoryMembers = None 856 857 # Map from category name to a map from an object name to the object 858 __categoryObjectMap = None 859 __PrivateTransient.add('categoryObjectMap') 860 861 # The set of objects that originated at this origin 862 __originatedObjects = None 863 __PrivateTransient.add('originatedObjects')
864
865 -class _SchemaOrigin (_ObjectOrigin):
866 """Holds the data regarding components derived from a single schema. 867 868 Coupled to a particular namespace through the 869 L{_NamespaceComponentAssociation_mixin}. 870 """ 871 872 __PrivateTransient = set() 873
874 - def __setDefaultKW (self, kw):
875 schema = kw.get('schema') 876 if schema is not None: 877 assert not ('location' in kw) 878 kw['location'] = schema.location() 879 assert not ('signature' in kw) 880 kw['signature'] = schema.signature() 881 assert not ('generation_uid' in kw) 882 kw['generation_uid'] = schema.generationUID() 883 assert not ('namespace' in kw) 884 kw['namespace'] = schema.targetNamespace() 885 assert not ('version' in kw) 886 kw['version'] = schema.schemaAttribute('version')
887
888 - def match (self, **kw):
889 """Determine whether this record matches the parameters. 890 891 @keyword schema: a L{pyxb.xmlschema.structures.Schema} instance from 892 which the other parameters are obtained. 893 @keyword location: a schema location (URI) 894 @keyword signature: a schema signature 895 @return: C{True} iff I{either} C{location} or C{signature} matches.""" 896 self.__setDefaultKW(kw) 897 location = kw.get('location') 898 if (location is not None) and (self.location() == location): 899 return True 900 signature = kw.get('signature') 901 if (signature is not None) and (self.signature() == signature): 902 return True 903 return False
904
905 - def location (self):
906 return self.__location
907 __location = None 908
909 - def schema (self):
910 return self.__schema
911 __schema = None 912 __PrivateTransient.add('schema') 913
914 - def version (self):
915 return self.__version
916 __version = None 917
918 - def __init__ (self, **kw):
919 self.__setDefaultKW(kw) 920 self.__schema = kw.pop('schema', None) 921 self.__location = kw.pop('location', None) 922 self.__version = kw.pop('version', None) 923 super(_SchemaOrigin, self).__init__(kw.pop('namespace'), kw.pop('generation_uid'), **kw)
924
925 - def __str__ (self):
926 rv = [ '_SchemaOrigin(%s@%s' % (self.namespace(), self.location()) ] 927 if self.version() is not None: 928 rv.append(',version=%s' % (self.version(),)) 929 rv.append(')') 930 return ''.join(rv)
931
932 -class NamespaceDependencies (object):
933
934 - def rootNamespaces (self):
935 return self.__rootNamespaces
936 __rootNamespaces = None 937
938 - def namespaceGraph (self, reset=False):
939 if reset or (self.__namespaceGraph is None): 940 self.__namespaceGraph = pyxb.utils.utility.Graph() 941 for ns in self.rootNamespaces(): 942 self.__namespaceGraph.addRoot(ns) 943 944 # Make sure all referenced namespaces have valid components 945 need_check = self.__rootNamespaces.copy() 946 done_check = set() 947 while 0 < len(need_check): 948 ns = need_check.pop() 949 ns.validateComponentModel() 950 self.__namespaceGraph.addNode(ns) 951 for rns in ns.referencedNamespaces().union(ns.importedNamespaces()): 952 self.__namespaceGraph.addEdge(ns, rns) 953 if not rns in done_check: 954 need_check.add(rns) 955 if not ns.hasSchemaComponents(): 956 _log.warning('Referenced %s has no schema components', ns.uri()) 957 done_check.add(ns) 958 assert done_check == self.__namespaceGraph.nodes() 959 960 return self.__namespaceGraph
961 __namespaceGraph = None 962
963 - def namespaceOrder (self, reset=False):
964 return self.namespaceGraph(reset).sccOrder()
965
966 - def siblingsFromGraph (self, reset=False):
967 siblings = set() 968 ns_graph = self.namespaceGraph(reset) 969 for ns in self.__rootNamespaces: 970 ns_siblings = ns_graph.sccMap().get(ns) 971 if ns_siblings is not None: 972 siblings.update(ns_siblings) 973 else: 974 siblings.add(ns) 975 return siblings
976
977 - def siblingNamespaces (self):
978 if self.__siblingNamespaces is None: 979 self.__siblingNamespaces = self.siblingsFromGraph() 980 return self.__siblingNamespaces
981
982 - def setSiblingNamespaces (self, sibling_namespaces):
983 self.__siblingNamespaces = sibling_namespaces
984 985 __siblingNamespaces = None 986
987 - def dependentNamespaces (self, reset=False):
988 return self.namespaceGraph(reset).nodes()
989
990 - def componentGraph (self, reset=False):
991 if reset or (self.__componentGraph is None): 992 self.__componentGraph = pyxb.utils.utility.Graph() 993 all_components = set() 994 for ns in self.siblingNamespaces(): 995 [ all_components.add(_c) for _c in ns.components() if _c.hasBinding() ] 996 997 need_visit = all_components.copy() 998 while 0 < len(need_visit): 999 c = need_visit.pop() 1000 self.__componentGraph.addNode(c) 1001 for cd in c.bindingRequires(include_lax=True): 1002 if cd in all_components: 1003 self.__componentGraph.addEdge(c, cd) 1004 return self.__componentGraph
1005 __componentGraph = None 1006
1007 - def componentOrder (self, reset=False):
1008 return self.componentGraph(reset).sccOrder()
1009
1010 - def __init__ (self, **kw):
1011 namespace_set = set(kw.get('namespace_set', [])) 1012 namespace = kw.get('namespace') 1013 if namespace is not None: 1014 namespace_set.add(namespace) 1015 if 0 == len(namespace_set): 1016 raise pyxb.LogicError('NamespaceDependencies requires at least one root namespace') 1017 self.__rootNamespaces = namespace_set
1018 1019 1020 ## Local Variables: 1021 ## fill-column:78 1022 ## End: 1023