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