1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Utility functions and classes."""
16
17 import re
18 import os
19 import errno
20 import pyxb
21
23 """Convert a string into a literal value that can be used in Python source.
24
25 This just calls C{repr}. No point in getting all complex when the language
26 already gives us what we need.
27
28 @rtype: C{str}
29 """
30 return repr(s)
31
32 _UnderscoreSubstitute_re = re.compile(r'[- .]')
33 _NonIdentifier_re = re.compile(r'[^a-zA-Z0-9_]')
34 _PrefixUnderscore_re = re.compile(r'^_+')
35 _PrefixDigit_re = re.compile(r'^\d+')
36
38 """Convert a string into something suitable to be a Python identifier.
39
40 The string is converted to unicode; spaces and periods replaced by
41 underscores; non-printables stripped. Furthermore, any leading
42 underscores are removed. If the result begins with a digit, the
43 character 'n' is prepended. If the result is the empty string,
44 the string 'emptyString' is substituted.
45
46 No check is made for L{conflicts with keywords <DeconflictKeyword>}.
47
48 @rtype: C{str}
49 """
50 s = _PrefixUnderscore_re.sub('', _NonIdentifier_re.sub('',_UnderscoreSubstitute_re.sub('_', str(s))))
51 if _PrefixDigit_re.match(s):
52 s = 'n' + s
53 if 0 == len(s):
54 s = 'emptyString'
55 return s
56
57
58 _Keywords = frozenset( ( "and", "del", "from", "not", "while", "as", "elif", "global",
59 "or", "with", "assert", "else", "if", "pass", "yield",
60 "break", "except", "import", "print", "class", "exec",
61 "in", "raise", "continue", "finally", "is", "return",
62 "def", "for", "lambda", "try",
63 "None" ) )
64 """The keywords reserved for Python."""
65
67 """If the provided string C{s} matches a Python language keyword,
68 append an underscore to distinguish them.
69
70 See also L{MakeUnique}.
71
72 @param s: string to be deconflicted
73
74 @keyword aux_keywords: optional iterable of additional strings
75 that should be treated as keywords.
76
77 @rtype: C{str}
78
79 """
80 if (s in _Keywords) or (s in aux_keywords):
81 return '%s_' % (s,)
82 return s
83
85 """Return an identifier based on C{s} that is not in the given set.
86
87 The returned identifier is made unique by appending an underscore
88 and, if necessary, a serial number.
89
90 The order is : C{x}, C{x_}, C{x_2}, C{x_3}, ...
91
92 @param in_use: The set of identifiers already in use in the
93 relevant scope. C{in_use} is updated to contain the returned
94 identifier.
95
96 @rtype: C{str}
97 """
98 if s in in_use:
99 ctr = 2
100 s = s.rstrip('_')
101 candidate = '%s_' % (s,)
102 while candidate in in_use:
103 candidate = '%s_%d' % (s, ctr)
104 ctr += 1
105 s = candidate
106 in_use.add(s)
107 return s
108
109 -def PrepareIdentifier (s, in_use, aux_keywords=frozenset(), private=False, protected=False):
110 """Combine everything required to create a unique identifier.
111
112 Leading and trailing underscores are stripped from all
113 identifiers.
114
115 @param in_use: the set of already used identifiers. Upon return
116 from this function, it is updated to include the returned
117 identifier.
118
119 @keyword aux_keywords: an optional set of additional symbols that
120 are illegal in the given context; use this to prevent conflicts
121 with known method names.
122
123 @keyword private: if C{False} (default), all leading underscores
124 are stripped, guaranteeing the identifier will not be private. If
125 C{True}, the returned identifier has two leading underscores,
126 making it a private variable within a Python class.
127
128 @keyword protected: as for C{private}, but uses only one
129 underscore.
130
131 @rtype: C{str}
132
133 @note: Only module-level identifiers should be treated as
134 protected. The class-level L{_DeconflictSymbols_mixin}
135 infrastructure does not include protected symbols. All class and
136 instance members beginning with a single underscore are reserved
137 for the PyXB infrastructure."""
138 s = DeconflictKeyword(MakeIdentifier(s).strip('_'), aux_keywords)
139 if private:
140 s = '__' + s
141 elif protected:
142 s = '_' + s
143 return MakeUnique(s, in_use)
144
145
147 """Mix-in used to deconflict public symbols in classes that may be
148 inherited by generated binding classes.
149
150 Some classes, like the L{pyxb.binding.basis.element} or
151 L{pyxb.binding.basis.simpleTypeDefinition} classes in
152 L{pyxb.binding.basis}, have public symbols associated with
153 functions and variables. It is possible that an XML schema might
154 include tags and attribute names that match these symbols. To
155 avoid conflict, the reserved symbols marked in this class are
156 added to the pre-defined identifier set.
157
158 Subclasses should create a class-level variable
159 C{_ReservedSymbols} that contains a set of strings denoting the
160 symbols reserved in this class, combined with those from any
161 superclasses that also have reserved symbols. Code like the
162 following is suggested::
163
164 # For base classes (direct mix-in):
165 _ReservedSymbols = set([ 'one', 'two' ])
166 # For subclasses:
167 _ReservedSymbols = SuperClass._ReservedSymbols.union(set([ 'three' ]))
168
169 Only public symbols (those with no underscores) are currently
170 supported. (Private symbols can't be deconflicted that easily,
171 and no protected symbols that derive from the XML are created by
172 the binding generator.)
173 """
174
175 _ReservedSymbols = set()
176 """There are no reserved symbols in the base class."""
177
178
179 __TabCRLF_re = re.compile("[\t\n\r]")
180
181 __MultiSpace_re = re.compile(" +")
182
184 """Normalize the given string.
185
186 Exactly one of the C{preserve}, C{replace}, and C{collapse} keyword
187 parameters must be assigned the value C{True} by the caller.
188
189 - C{preserve}: the text is returned unchanged.
190
191 - C{replace}: all tabs, newlines, and carriage returns are
192 replaced with ASCII spaces.
193
194 - C{collapse}: the C{replace} normalization is done, then
195 sequences of two or more spaces are replaced by a single space.
196
197 See the U{whiteSpace facet<http://www.w3.org/TR/xmlschema-2/#rf-whiteSpace>}.
198
199 @rtype: C{str}
200 """
201 if preserve:
202 return text
203 text = __TabCRLF_re.sub(' ', text)
204 if replace:
205 return text
206 if collapse:
207 return __MultiSpace_re.sub(' ', text).strip()
208
209 raise Exception('NormalizeWhitespace: No normalization specified')
210
212 """Represent a directed graph with arbitrary objects as nodes.
213
214 This is used in the L{code
215 generator<pyxb.binding.generate.Generator>} to determine order
216 dependencies among components within a namespace, and schema that
217 comprise various namespaces. An edge from C{source} to C{target}
218 indicates that some aspect of C{source} requires that some aspect
219 of C{target} already be available.
220 """
221
223 self.__roots = None
224 if root is not None:
225 self.__roots = set([root])
226 self.__edges = set()
227 self.__edgeMap = { }
228 self.__reverseMap = { }
229 self.__nodes = set()
230
231 __scc = None
232 __sccMap = None
233 __dfsOrder = None
234
235 - def addEdge (self, source, target):
236 """Add a directed edge from the C{source} to the C{target}.
237
238 The nodes are added to the graph if necessary.
239 """
240 self.__edges.add( (source, target) )
241 self.__edgeMap.setdefault(source, set()).add(target)
242 if source != target:
243 self.__reverseMap.setdefault(target, set()).add(source)
244 self.__nodes.add(source)
245 self.__nodes.add(target)
246
248 """Add the given node to the graph."""
249 self.__nodes.add(node)
250
251 __roots = None
252 - def roots (self, reset=False):
253 """Return the set of nodes calculated to be roots (i.e., those that have no incoming edges).
254
255 This caches the roots calculated in a previous invocation
256 unless the C{reset} keyword is given the value C{True}.
257
258 @note: Upon reset, any notes that had been manually added
259 using L{addNode} will no longer be in the set.
260
261 @keyword reset: If C{True}, any cached value is discarded and
262 recomputed. No effect if C{False} (defalut).
263
264 @rtype: C{set}
265 """
266 if reset or (self.__roots is None):
267 self.__roots = set()
268 for n in self.__nodes:
269 if not (n in self.__reverseMap):
270 self.__roots.add(n)
271 return self.__roots
273 """Add the provided node as a root node, even if it has incoming edges.
274
275 The node need not be present in the graph (if necessary, it is added).
276
277 Note that roots added in this way do not survive a reset using
278 L{roots}.
279
280 @return: C{self}
281 """
282 if self.__roots is None:
283 self.__roots = set()
284 self.__nodes.add(root)
285 self.__roots.add(root)
286 return self
287
289 """Return the edges in the graph.
290
291 The edge data structure is a map from the source node to the
292 set of nodes that can be reached in a single step from the
293 source.
294 """
295 return self.__edgeMap
296 __edgeMap = None
297
299 """Return the edges in the graph.
300
301 The edge data structure is a set of node pairs represented as C{( source, target )}.
302 """
303 return self.__edges
304
306 """Return the set of nodes in the graph.
307
308 The node collection data structure is a set containing node
309 objects, whatever they may be."""
310 return self.__nodes
311
312 - def tarjan (self, reset=False):
313 """Execute Tarjan's algorithm on the graph.
314
315 U{Tarjan's
316 algorithm<http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm>}
317 computes the U{strongly-connected
318 components<http://en.wikipedia.org/wiki/Strongly_connected_component>}
319 of the graph: i.e., the sets of nodes that form a minimal
320 closed set under edge transition. In essence, the loops. We
321 use this to detect groups of components that have a dependency
322 cycle.
323
324 @keyword reset: If C{True}, any cached component set is erased
325 and recomputed. If C{True}, an existing previous result is
326 left unchanged."""
327
328 if (self.__scc is not None) and (not reset):
329 return
330 self.__sccMap = { }
331 self.__stack = []
332 self.__sccOrder = []
333 self.__scc = []
334 self.__index = 0
335 self.__tarjanIndex = { }
336 self.__tarjanLowLink = { }
337 for v in self.__nodes:
338 self.__tarjanIndex[v] = None
339 roots = self.roots()
340 if (0 == len(roots)) and (0 < len(self.__nodes)):
341 raise Exception('TARJAN: No roots found in graph with %d nodes' % (len(self.__nodes),))
342 for r in roots:
343 self._tarjan(r)
344 self.__didTarjan = True
345
347 """Do the work of Tarjan's algorithm for a given root node."""
348 if self.__tarjanIndex.get(v) is not None:
349
350 return
351 self.__tarjanIndex[v] = self.__tarjanLowLink[v] = self.__index
352 self.__index += 1
353 self.__stack.append(v)
354 source = v
355 for target in self.__edgeMap.get(source, []):
356 if self.__tarjanIndex[target] is None:
357
358 self._tarjan(target)
359 self.__tarjanLowLink[v] = min(self.__tarjanLowLink[v], self.__tarjanLowLink[target])
360 elif target in self.__stack:
361
362 self.__tarjanLowLink[v] = min(self.__tarjanLowLink[v], self.__tarjanLowLink[target])
363 else:
364
365 pass
366
367 if self.__tarjanLowLink[v] == self.__tarjanIndex[v]:
368 scc = []
369 while True:
370 scc.append(self.__stack.pop())
371 if v == scc[-1]:
372 break;
373 self.__sccOrder.append(scc)
374 if 1 < len(scc):
375 self.__scc.append(scc)
376 [ self.__sccMap.setdefault(_v, scc) for _v in scc ]
377
378
379 - def scc (self, reset=False):
380 """Return the strongly-connected components of the graph.
381
382 The data structure is a set, each element of which is itself a
383 set containing one or more nodes from the graph.
384
385 @see: L{tarjan}.
386 """
387 if reset or (self.__scc is None):
388 self.tarjan(reset)
389 return self.__scc
390 __scc = None
391
392 - def sccMap (self, reset=False):
393 """Return a map from nodes to the strongly-connected component
394 to which the node belongs.
395
396 @keyword reset: If C{True}, the L{tarjan} method will be
397 re-invoked, propagating the C{reset} value. If C{False}
398 (default), a cached value will be returned if available.
399
400 @see: L{tarjan}.
401 """
402 if reset or (self.__sccMap is None):
403 self.tarjan(reset)
404 return self.__sccMap
405 __sccMap = None
406
408 """Return the strongly-connected components in order.
409
410 The data structure is a list, in dependency order, of strongly
411 connected components (which can be single nodes). Appearance
412 of a node in a set earlier in the list indicates that it has
413 no dependencies on any node that appears in a subsequent set.
414 This order is preferred over L{dfsOrder} for code generation,
415 since it detects loops.
416
417 @see: L{tarjan}.
418 """
419 if reset or (self.__sccOrder is None):
420 self.tarjan(reset)
421 return self.__sccOrder
422 __sccOrder = None
423
425 """Return the strongly-connected component to which the given
426 node belongs.
427
428 Any keywords suppliend when invoking this method are passed to
429 the L{sccMap} method.
430
431 @return: The SCC set, or C{None} if the node is not present in
432 the results of Tarjan's algorithm."""
433
434 return self.sccMap(**kw).get(node, None)
435
437 """Return the cyclomatic complexity of the graph."""
438 self.tarjan()
439 return len(self.__edges) - len(self.__nodes) + 2 * len(self.__scc)
440
442 assert not (source in self.__dfsWalked)
443 self.__dfsWalked.add(source)
444 for target in self.__edgeMap.get(source, []):
445 if not (target in self.__dfsWalked):
446 self.__dfsWalk(target)
447 self.__dfsOrder.append(source)
448
450 node_map = { }
451 idx = 1
452 for n in self.__nodes:
453 node_map[n] = idx
454 idx += 1
455 text = []
456 text.append('digraph "%s" {' % (title,))
457 for n in self.__nodes:
458 if labeller is not None:
459 nn = labeller(n)
460 else:
461 nn = str(n)
462 text.append('%s [shape=box,label="%s"];' % (node_map[n], nn))
463 for s in self.__nodes:
464 for d in self.__edgeMap.get(s, []):
465 if s != d:
466 text.append('%s -> %s;' % (node_map[s], node_map[d]))
467 text.append("};")
468 return "\n".join(text)
469
471 """Return the nodes of the graph in U{depth-first-search
472 order<http://en.wikipedia.org/wiki/Depth-first_search>}.
473
474 The data structure is a list. Calculated lists are retained
475 and returned on future invocations, subject to the C{reset}
476 keyword.
477
478 @keyword reset: If C{True}, discard cached results and recompute the order."""
479 if reset or (self.__dfsOrder is None):
480 self.__dfsWalked = set()
481 self.__dfsOrder = []
482 for root in self.roots(reset=reset):
483 self.__dfsWalk(root)
484 self.__dfsWalked = None
485 if len(self.__dfsOrder) != len(self.__nodes):
486 raise Exception('DFS walk did not cover all nodes (walk %d versus nodes %d)' % (len(self.__dfsOrder), len(self.__nodes)))
487 return self.__dfsOrder
488
490 """Normalize a URI against an optional parent_uri in the way that is
491 done for C{schemaLocation} attribute values.
492
493 If no URI schema is present, this will normalize a file system
494 path."""
495 import urlparse
496 import os
497
498 if uri is None:
499 return uri
500 if parent_uri is None:
501 abs_uri = uri
502 else:
503
504
505 abs_uri = urlparse.urljoin(parent_uri, uri)
506 if 0 > abs_uri.find(':'):
507 abs_uri = os.path.realpath(abs_uri)
508 return abs_uri
509
510 import urlparse
511
512 -def TextFromURI (uri, archive_directory=None):
513 """Retrieve the contents of the uri as a text string.
514
515 If the uri does not include a scheme (e.g., C{http:}), it is
516 assumed to be a file path on the local system."""
517 import urllib2
518 xmls = None
519 try:
520 if 0 <= uri.find(':'):
521 xmls = urllib2.urlopen(uri).read()
522 else:
523 xmls = file(uri).read()
524 archive_directory = None
525 except Exception, e:
526 print 'TextFromURI: open %s caught: %s' % (uri, e)
527 raise
528 if archive_directory:
529 base_name = os.path.basename(os.path.normpath(urlparse.urlparse(uri)[2]))
530 counter = 1
531 dest_file = os.path.join(archive_directory, base_name)
532 while os.path.isfile(dest_file):
533 dest_file = os.path.join(archive_directory, '%s.%d' % (base_name, counter))
534 counter += 1
535 try:
536 OpenOrCreate(dest_file).write(xmls)
537 except OSError, e:
538 print 'WARNING: Unable to save %s in %s: %s' % (uri, dest_file, e)
539 return xmls
540
541 -def OpenOrCreate (file_name, tag=None, preserve_contents=False):
542 """Return a file object used to write the given file.
543
544 Use the C{tag} keyword to preserve the contents of existing files
545 that are not supposed to be overwritten.
546
547 To get a writable file but leaving any existing contents in place,
548 set the C{preserve_contents} keyword to C{True}. Normally, existing file
549 contents are erased.
550
551 The returned file pointer is positioned at the end of the file.
552
553 @keyword tag: If not C{None} and the file already exists, absence
554 of the given value in the first 4096 bytes of the file causes an
555 C{IOError} to be raised with C{errno} set to C{EEXIST}. I.e.,
556 only files with this value in the first 4KB will be returned for
557 writing.
558
559 @keyword preserve_contents: This value controls whether existing
560 contents of the file will be erased (C{False}, default) or left in
561 place (C{True}).
562 """
563 (path, leaf) = os.path.split(file_name)
564 if path:
565 try:
566 os.makedirs(path)
567 except Exception, e:
568 if not (isinstance(e, (OSError, IOError)) and (errno.EEXIST == e.errno)):
569 raise
570 fp = file(file_name, 'a+')
571 if (tag is not None) and (0 < os.fstat(fp.fileno()).st_size):
572 text = fp.read(4096)
573 if 0 > text.find(tag):
574 raise OSError(errno.EEXIST, os.strerror(errno.EEXIST))
575 if not preserve_contents:
576 fp.seek(0)
577 fp.truncate()
578 else:
579 fp.seek(2)
580 return fp
581
582
583 __Hasher = None
584 try:
585 import hashlib
586 __Hasher = hashlib.sha1
587 except ImportError:
588 import sha
589 __Hasher = sha.new
590
591 -def HashForText (text):
592 """Calculate a cryptographic hash of the given string.
593
594 For example, this is used to verify that a given module file
595 contains bindings from a previous generation run for the same
596 namespace. See L{OpenOrCreate}.
597
598 @return: A C{str}, generally a sequence of hexadecimal "digit"s.
599 """
600 return __Hasher(text).hexdigest()
601
602
603 __HaveUUID = False
604 try:
605 import uuid
606 __HaveUUID = True
607 except ImportError:
608 import time
609 import random
611 """Obtain a UUID using the best available method. On a version of
612 python that does not incorporate the C{uuid} class, this creates a
613 string combining the current date and time (to the second) with a
614 random number.
615
616 @rtype: C{str}
617 """
618 if __HaveUUID:
619 return uuid.uuid1().urn
620 return '%s:%08.8x' % (time.strftime('%Y%m%d%H%M%S'), random.randint(0, 0xFFFFFFFFL))
621
623 """Records a unique identifier, generally associated with a
624 binding generation action.
625
626 The identifier is a string, but gets wrapped in an instance of
627 this class to optimize comparisons and reduce memory footprint.
628
629 Invoking the constructor for this class on the same string
630 multiple times will return the same Python object.
631
632 An instance of this class compares equal to, and hashes equivalent
633 to, the uid string. When C{str}'d, the result is the uid; when
634 C{repr}'d, the result is a constructor call to
635 C{pyxb.utils.utility.UniqueIdentifier}.
636 """
637
638
639 __ExistingUIDs = {}
640
642 """The string unique identifier"""
643 return self.__uid
644 __uid = None
645
646
649
652
655
656
672
674 """Associate the given object witth this identifier.
675
676 This is a one-way associatoin: the object is not provided with
677 a return path to this identifier instance."""
678 self.__associatedObjects.add(obj)
680 """The set of objects that have been associated with this
681 identifier instance."""
682 return self.__associatedObjects
683 __associatedObjects = None
684
686 """Create a new UniqueIdentifier instance.
687
688 @param uid: The unique identifier string. If present, it is
689 the callers responsibility to ensure the value is universally
690 unique. If C{None}, one will be provided.
691 @type uid: C{str} or C{unicode}
692 """
693 assert (uid is None) or (self.uid() == uid), 'UniqueIdentifier: ctor %s, actual %s' % (uid, self.uid())
694 self.__associatedObjects = set()
695
697 if other is None:
698 return False
699 elif isinstance(other, UniqueIdentifier):
700 other_uid = other.uid()
701 elif isinstance(other, basestring):
702 other_uid = other
703 else:
704 raise TypeError('UniqueIdentifier: Cannot compare with type %s' % (type(other),))
705 return self.uid() == other_uid
706
708 return hash(self.uid())
709
712
714 return 'pyxb.utils.utility.UniqueIdentifier(%s)' % (repr(self.uid()),)
715
716 import datetime
717 import calendar
718 import time
720 """A C{datetime.tzinfo} subclass that helps deal with UTC
721 conversions in an ISO8601 world.
722
723 This class only supports fixed offsets from UTC.
724 """
725
726
727 __Lexical_re = re.compile('^([-+])(\d\d):(\d\d)$')
728
729
730 __utcOffset_min = 0
731
732
733 __utcOffset_td = None
734
735
736 __ZeroDuration = datetime.timedelta(0)
737
738 - def __init__ (self, spec=None, flip=False):
739 """Create a time zone instance with a fixed offset from UTC.
740
741 @param spec: Specifies the offset. Can be an integer counting
742 minutes east of UTC, the value C{None} (equal to 0 minutes
743 east), or a string that conform to the ISO8601 time zone
744 sequence (B{Z}, or B{[+-]HH:MM}).
745
746 @param flip: If C{False} (default), no adaptation is done. If
747 C{True}, the time zone offset is negated, resulting in the
748 conversion from localtime to UTC rather than the default of
749 UTC to localtime.
750 """
751
752 if spec is not None:
753 if isinstance(spec, basestring):
754 if 'Z' == spec:
755 self.__utcOffset_min = 0
756 else:
757 match = self.__Lexical_re.match(spec)
758 if match is None:
759 raise ValueError('Bad time zone: %s' % (spec,))
760 self.__utcOffset_min = int(match.group(2)) * 60 + int(match.group(3))
761 if '-' == match.group(1):
762 self.__utcOffset_min = - self.__utcOffset_min
763 elif isinstance(spec, int):
764 self.__utcOffset_min = spec
765 elif isinstance(spec, datetime.timedelta):
766 self.__utcOffset_min = spec.seconds / 60
767 else:
768 raise TypeError('%s: unexpected type %s' % (type(self), type(spec)))
769 if flip:
770 self.__utcOffset_min = - self.__utcOffset_min
771 self.__utcOffset_td = datetime.timedelta(minutes=self.__utcOffset_min)
772 if 0 == self.__utcOffset_min:
773 self.__tzName = 'Z'
774 elif 0 > self.__utcOffset_min:
775 self.__tzName = '-%02d%02d' % divmod(-self.__utcOffset_min, 60)
776 else:
777 self.__tzName = '+%02d%02d' % divmod(self.__utcOffset_min, 60)
778
780 """Returns the constant offset for this zone."""
781 return self.__utcOffset_td
782
784 """Return the name of the timezone in ISO8601 format."""
785 return self.__tzName
786
787 - def dst (self, dt):
788 """Returns a constant zero duration."""
789 return self.__ZeroDuration
790
792 """A C{datetime.tzinfo} subclass for the local time zone.
793
794 Mostly pinched from the C{datetime.tzinfo} documentation in Python 2.5.1.
795 """
796
797 __STDOffset = datetime.timedelta(seconds=-time.timezone)
798 __DSTOffset = __STDOffset
799 if time.daylight:
800 __DSTOffset = datetime.timedelta(seconds=-time.altzone)
801 __ZeroDelta = datetime.timedelta(0)
802 __DSTDelta = __DSTOffset - __STDOffset
803
808
809 - def dst (self, dt):
813
816
818 tt = (dt.year, dt.month, dt.day,
819 dt.hour, dt.minute, dt.second,
820 0, 0, -1)
821 tt = time.localtime(time.mktime(tt))
822 return tt.tm_isdst > 0
823
825 """Emulate the B{transient} keyword from Java for private member
826 variables.
827
828 This class defines a C{__getstate__} method which returns a copy
829 of C{self.__dict__} with certain members removed. Specifically,
830 if a string "s" appears in a class member variable named
831 C{__PrivateTransient} defined in the "Class" class, then the
832 corresponding private variable "_Class__s" will be removed from
833 the state dictionary. This is used to eliminate unnecessary
834 fields from instances placed in L{namespace
835 archives<pyxb.namespace.archive.NamespaceArchive>} without having
836 to implement a C{__getstate__} method in every class in the
837 instance hierarchy.
838
839 For an example, see
840 L{pyxb.xmlschema.structures._SchemaComponent_mixin}
841
842 If you use this, it is your responsibility to define the
843 C{__PrivateTransient} class variable and add to it the required
844 variable names.
845
846 Classes that inherit from this are free to define their own
847 C{__getstate__} method, which may or may not invoke the superclass
848 one. If you do this, be sure that the class defining
849 C{__getstate__} lists L{PrivateTransient_mixin} as one of its
850 direct superclasses, lest the latter end up earlier in the mro and
851 consequently bypass the local override.
852 """
853
854
855
856 __Attribute = '__PrivateTransient'
857
859 state = self.__dict__.copy()
860
861
862 attr = '_%s%s_' % (self.__class__.__name__, self.__Attribute)
863 skipped = getattr(self.__class__, attr, None)
864 if skipped is None:
865 skipped = set()
866 for cl in self.__class__.mro():
867 for (k, v) in cl.__dict__.items():
868 if k.endswith(self.__Attribute):
869 cl2 = k[:-len(self.__Attribute)]
870 skipped.update([ '%s__%s' % (cl2, _n) for _n in v ])
871 setattr(self.__class__, attr, skipped)
872
873 for k in skipped:
874 if state.get(k) is not None:
875
876 del state[k]
877
878
879
880
881
882
883
884
885
886
887 return state
888
889 -def GetMatchingFiles (path, pattern=None, default_path_wildcard=None, default_path=None, prefix_pattern=None, prefix_substituend=None):
890 """Provide a list of absolute paths to files present in any of a
891 set of directories and meeting certain criteria.
892
893 This is used, for example, to locate namespace archive files
894 within the archive path specified by the user. One could use::
895
896 files = GetMatchingFiles('&bundles//:+',
897 pattern=re.compile('.*\.wxs$'),
898 default_path_wildcard='+',
899 default_path='/usr/local/pyxb/nsarchives',
900 prefix_pattern='&',
901 prefix_substituend='/opt/pyxb')
902
903 to obtain all files that can be recursively found within
904 C{/opt/pyxb/bundles}, or non-recursively within
905 C{/usr/local/pyxb/nsarchives}.
906
907 @param path: A colon separated list of directories in which the
908 search should be performed. If a path entry ends with C{//}, any
909 directory beneath it is scanned as well, recursively.
910
911 @keyword pattern: Optional regular expression object used to
912 determine whether a given directory entry should be returned. If
913 left as C{None}, all directory entries will be returned.
914
915 @keyword default_path_wildcard: An optional string which, if
916 present as a single directory in the path, is replaced by the
917 value of C{default-path}.
918
919 @keyword default_path: A system-defined directory which can be
920 restored to the path by placing the C{default_path_wildcard} in
921 the C{path}.
922
923 @keyword prefix_pattern: An optional string which, if present at
924 the start of a path element, is replaced by the value of
925 C{prefix_substituend}.
926
927 @keyword prefix_substituend: A system-defined string (path prefix)
928 which can be combined with the user-provided path information to
929 identify a file or subdirectory within an installation-specific
930 area.
931 """
932 matching_files = []
933 path_set = path.split(':')
934 while 0 < len(path_set):
935 path = path_set.pop(0)
936 if default_path_wildcard == path:
937 if default_path is not None:
938 path_set[0:0] = default_path.split(':')
939 continue
940 recursive = False
941 if (prefix_pattern is not None) and path.startswith(prefix_pattern):
942 path = os.path.join(prefix_substituend, path[len(prefix_pattern):])
943 if path.endswith('//'):
944 recursive = True
945 path = path[:-2]
946 if os.path.isfile(path):
947 if (pattern is None) or (pattern.search(path) is not None):
948 matching_files.append(path)
949 else:
950 for (root, dirs, files) in os.walk(path):
951 for f in files:
952 if (pattern is None) or (pattern.search(f) is not None):
953 matching_files.append(os.path.join(root, f))
954 if not recursive:
955 break
956 return matching_files
957
959 """Wrap a location.
960
961 This is probably a string, but might be a uri object or the like.
962 Really we only have this as a separate object so as to avoid
963 creating hundreds of copies of the same string."""
964
965 __locationBase = None
968
973
976
1006
1019