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
     359 -    def _unsatisfiedModulePrerequisites (self):
     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):
     562          self.__wroteToArchive = 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):
     592          self.__isImportAugmentable = value
     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):
     714          self.__referencedNamespaces.add(namespace)
     715          return 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
     801 -    def completeGenerationAssociations (self):
     802          self.namespace()._transferReferencedNamespaces(self)
     803          self.namespace()._associateOrigins(self)
     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