1 | # util.py |
---|
2 | # Copyright (C) 2005, 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com |
---|
3 | # |
---|
4 | # This module is part of SQLAlchemy and is released under |
---|
5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php |
---|
6 | |
---|
7 | import inspect, itertools, operator, sys, warnings, weakref |
---|
8 | import __builtin__ |
---|
9 | types = __import__('types') |
---|
10 | |
---|
11 | from sqlalchemy import exc |
---|
12 | |
---|
13 | try: |
---|
14 | import threading |
---|
15 | except ImportError: |
---|
16 | import dummy_threading as threading |
---|
17 | |
---|
18 | py3k = getattr(sys, 'py3kwarning', False) or sys.version_info >= (3, 0) |
---|
19 | |
---|
20 | if py3k: |
---|
21 | set_types = set |
---|
22 | elif sys.version_info < (2, 6): |
---|
23 | import sets |
---|
24 | set_types = set, sets.Set |
---|
25 | else: |
---|
26 | # 2.6 deprecates sets.Set, but we still need to be able to detect them |
---|
27 | # in user code and as return values from DB-APIs |
---|
28 | ignore = ('ignore', None, DeprecationWarning, None, 0) |
---|
29 | try: |
---|
30 | warnings.filters.insert(0, ignore) |
---|
31 | except Exception: |
---|
32 | import sets |
---|
33 | else: |
---|
34 | import sets |
---|
35 | warnings.filters.remove(ignore) |
---|
36 | |
---|
37 | set_types = set, sets.Set |
---|
38 | |
---|
39 | EMPTY_SET = frozenset() |
---|
40 | |
---|
41 | if py3k: |
---|
42 | import pickle |
---|
43 | else: |
---|
44 | try: |
---|
45 | import cPickle as pickle |
---|
46 | except ImportError: |
---|
47 | import pickle |
---|
48 | |
---|
49 | # a controversial feature, required by MySQLdb currently |
---|
50 | def buffer(x): |
---|
51 | return x |
---|
52 | |
---|
53 | buffer = getattr(__builtin__, 'buffer', buffer) |
---|
54 | |
---|
55 | if sys.version_info >= (2, 5): |
---|
56 | class PopulateDict(dict): |
---|
57 | """A dict which populates missing values via a creation function. |
---|
58 | |
---|
59 | Note the creation function takes a key, unlike |
---|
60 | collections.defaultdict. |
---|
61 | |
---|
62 | """ |
---|
63 | |
---|
64 | def __init__(self, creator): |
---|
65 | self.creator = creator |
---|
66 | |
---|
67 | def __missing__(self, key): |
---|
68 | self[key] = val = self.creator(key) |
---|
69 | return val |
---|
70 | else: |
---|
71 | class PopulateDict(dict): |
---|
72 | """A dict which populates missing values via a creation function.""" |
---|
73 | |
---|
74 | def __init__(self, creator): |
---|
75 | self.creator = creator |
---|
76 | |
---|
77 | def __getitem__(self, key): |
---|
78 | try: |
---|
79 | return dict.__getitem__(self, key) |
---|
80 | except KeyError: |
---|
81 | self[key] = value = self.creator(key) |
---|
82 | return value |
---|
83 | |
---|
84 | if py3k: |
---|
85 | def callable(fn): |
---|
86 | return hasattr(fn, '__call__') |
---|
87 | else: |
---|
88 | callable = __builtin__.callable |
---|
89 | |
---|
90 | if py3k: |
---|
91 | from functools import reduce |
---|
92 | else: |
---|
93 | reduce = __builtin__.reduce |
---|
94 | |
---|
95 | try: |
---|
96 | from collections import defaultdict |
---|
97 | except ImportError: |
---|
98 | class defaultdict(dict): |
---|
99 | def __init__(self, default_factory=None, *a, **kw): |
---|
100 | if (default_factory is not None and |
---|
101 | not hasattr(default_factory, '__call__')): |
---|
102 | raise TypeError('first argument must be callable') |
---|
103 | dict.__init__(self, *a, **kw) |
---|
104 | self.default_factory = default_factory |
---|
105 | def __getitem__(self, key): |
---|
106 | try: |
---|
107 | return dict.__getitem__(self, key) |
---|
108 | except KeyError: |
---|
109 | return self.__missing__(key) |
---|
110 | def __missing__(self, key): |
---|
111 | if self.default_factory is None: |
---|
112 | raise KeyError(key) |
---|
113 | self[key] = value = self.default_factory() |
---|
114 | return value |
---|
115 | def __reduce__(self): |
---|
116 | if self.default_factory is None: |
---|
117 | args = tuple() |
---|
118 | else: |
---|
119 | args = self.default_factory, |
---|
120 | return type(self), args, None, None, self.iteritems() |
---|
121 | def copy(self): |
---|
122 | return self.__copy__() |
---|
123 | def __copy__(self): |
---|
124 | return type(self)(self.default_factory, self) |
---|
125 | def __deepcopy__(self, memo): |
---|
126 | import copy |
---|
127 | return type(self)(self.default_factory, |
---|
128 | copy.deepcopy(self.items())) |
---|
129 | def __repr__(self): |
---|
130 | return 'defaultdict(%s, %s)' % (self.default_factory, |
---|
131 | dict.__repr__(self)) |
---|
132 | |
---|
133 | |
---|
134 | def to_list(x, default=None): |
---|
135 | if x is None: |
---|
136 | return default |
---|
137 | if not isinstance(x, (list, tuple)): |
---|
138 | return [x] |
---|
139 | else: |
---|
140 | return x |
---|
141 | |
---|
142 | def to_set(x): |
---|
143 | if x is None: |
---|
144 | return set() |
---|
145 | if not isinstance(x, set): |
---|
146 | return set(to_list(x)) |
---|
147 | else: |
---|
148 | return x |
---|
149 | |
---|
150 | def to_column_set(x): |
---|
151 | if x is None: |
---|
152 | return column_set() |
---|
153 | if not isinstance(x, column_set): |
---|
154 | return column_set(to_list(x)) |
---|
155 | else: |
---|
156 | return x |
---|
157 | |
---|
158 | |
---|
159 | try: |
---|
160 | from functools import update_wrapper |
---|
161 | except ImportError: |
---|
162 | def update_wrapper(wrapper, wrapped, |
---|
163 | assigned=('__doc__', '__module__', '__name__'), |
---|
164 | updated=('__dict__',)): |
---|
165 | for attr in assigned: |
---|
166 | setattr(wrapper, attr, getattr(wrapped, attr)) |
---|
167 | for attr in updated: |
---|
168 | getattr(wrapper, attr).update(getattr(wrapped, attr, ())) |
---|
169 | return wrapper |
---|
170 | |
---|
171 | try: |
---|
172 | from functools import partial |
---|
173 | except: |
---|
174 | def partial(func, *args, **keywords): |
---|
175 | def newfunc(*fargs, **fkeywords): |
---|
176 | newkeywords = keywords.copy() |
---|
177 | newkeywords.update(fkeywords) |
---|
178 | return func(*(args + fargs), **newkeywords) |
---|
179 | return newfunc |
---|
180 | |
---|
181 | |
---|
182 | def accepts_a_list_as_starargs(list_deprecation=None): |
---|
183 | def decorate(fn): |
---|
184 | |
---|
185 | spec = inspect.getargspec(fn) |
---|
186 | assert spec[1], 'Decorated function does not accept *args' |
---|
187 | |
---|
188 | def _deprecate(): |
---|
189 | if list_deprecation: |
---|
190 | if list_deprecation == 'pending': |
---|
191 | warning_type = exc.SAPendingDeprecationWarning |
---|
192 | else: |
---|
193 | warning_type = exc.SADeprecationWarning |
---|
194 | msg = ( |
---|
195 | "%s%s now accepts multiple %s arguments as a " |
---|
196 | "variable argument list. Supplying %s as a single " |
---|
197 | "list is deprecated and support will be removed " |
---|
198 | "in a future release." % ( |
---|
199 | fn.func_name, |
---|
200 | inspect.formatargspec(*spec), |
---|
201 | spec[1], spec[1])) |
---|
202 | warnings.warn(msg, warning_type, stacklevel=3) |
---|
203 | |
---|
204 | def go(fn, *args, **kw): |
---|
205 | if isinstance(args[-1], list): |
---|
206 | _deprecate() |
---|
207 | return fn(*(list(args[0:-1]) + args[-1]), **kw) |
---|
208 | else: |
---|
209 | return fn(*args, **kw) |
---|
210 | |
---|
211 | return decorator(go)(fn) |
---|
212 | |
---|
213 | return decorate |
---|
214 | |
---|
215 | def unique_symbols(used, *bases): |
---|
216 | used = set(used) |
---|
217 | for base in bases: |
---|
218 | pool = itertools.chain((base,), |
---|
219 | itertools.imap(lambda i: base + str(i), |
---|
220 | xrange(1000))) |
---|
221 | for sym in pool: |
---|
222 | if sym not in used: |
---|
223 | used.add(sym) |
---|
224 | yield sym |
---|
225 | break |
---|
226 | else: |
---|
227 | raise NameError("exhausted namespace for symbol base %s" % base) |
---|
228 | |
---|
229 | def decorator(target): |
---|
230 | """A signature-matching decorator factory.""" |
---|
231 | |
---|
232 | def decorate(fn): |
---|
233 | spec = inspect.getargspec(fn) |
---|
234 | names = tuple(spec[0]) + spec[1:3] + (fn.func_name,) |
---|
235 | targ_name, fn_name = unique_symbols(names, 'target', 'fn') |
---|
236 | |
---|
237 | metadata = dict(target=targ_name, fn=fn_name) |
---|
238 | metadata.update(format_argspec_plus(spec, grouped=False)) |
---|
239 | |
---|
240 | code = 'lambda %(args)s: %(target)s(%(fn)s, %(apply_kw)s)' % ( |
---|
241 | metadata) |
---|
242 | decorated = eval(code, {targ_name:target, fn_name:fn}) |
---|
243 | decorated.func_defaults = getattr(fn, 'im_func', fn).func_defaults |
---|
244 | return update_wrapper(decorated, fn) |
---|
245 | return update_wrapper(decorate, target) |
---|
246 | |
---|
247 | |
---|
248 | if sys.version_info >= (2, 5): |
---|
249 | def decode_slice(slc): |
---|
250 | """decode a slice object as sent to __getitem__. |
---|
251 | |
---|
252 | takes into account the 2.5 __index__() method, basically. |
---|
253 | |
---|
254 | """ |
---|
255 | ret = [] |
---|
256 | for x in slc.start, slc.stop, slc.step: |
---|
257 | if hasattr(x, '__index__'): |
---|
258 | x = x.__index__() |
---|
259 | ret.append(x) |
---|
260 | return tuple(ret) |
---|
261 | else: |
---|
262 | def decode_slice(slc): |
---|
263 | return (slc.start, slc.stop, slc.step) |
---|
264 | |
---|
265 | def flatten_iterator(x): |
---|
266 | """Given an iterator of which further sub-elements may also be |
---|
267 | iterators, flatten the sub-elements into a single iterator. |
---|
268 | |
---|
269 | """ |
---|
270 | for elem in x: |
---|
271 | if not isinstance(elem, basestring) and hasattr(elem, '__iter__'): |
---|
272 | for y in flatten_iterator(elem): |
---|
273 | yield y |
---|
274 | else: |
---|
275 | yield elem |
---|
276 | |
---|
277 | def get_cls_kwargs(cls): |
---|
278 | """Return the full set of inherited kwargs for the given `cls`. |
---|
279 | |
---|
280 | Probes a class's __init__ method, collecting all named arguments. If the |
---|
281 | __init__ defines a \**kwargs catch-all, then the constructor is presumed to |
---|
282 | pass along unrecognized keywords to it's base classes, and the collection |
---|
283 | process is repeated recursively on each of the bases. |
---|
284 | |
---|
285 | """ |
---|
286 | |
---|
287 | for c in cls.__mro__: |
---|
288 | if '__init__' in c.__dict__: |
---|
289 | stack = set([c]) |
---|
290 | break |
---|
291 | else: |
---|
292 | return [] |
---|
293 | |
---|
294 | args = set() |
---|
295 | while stack: |
---|
296 | class_ = stack.pop() |
---|
297 | ctr = class_.__dict__.get('__init__', False) |
---|
298 | if not ctr or not isinstance(ctr, types.FunctionType): |
---|
299 | continue |
---|
300 | names, _, has_kw, _ = inspect.getargspec(ctr) |
---|
301 | args.update(names) |
---|
302 | if has_kw: |
---|
303 | stack.update(class_.__bases__) |
---|
304 | args.discard('self') |
---|
305 | return list(args) |
---|
306 | |
---|
307 | def get_func_kwargs(func): |
---|
308 | """Return the full set of legal kwargs for the given `func`.""" |
---|
309 | return inspect.getargspec(func)[0] |
---|
310 | |
---|
311 | def format_argspec_plus(fn, grouped=True): |
---|
312 | """Returns a dictionary of formatted, introspected function arguments. |
---|
313 | |
---|
314 | A enhanced variant of inspect.formatargspec to support code generation. |
---|
315 | |
---|
316 | fn |
---|
317 | An inspectable callable or tuple of inspect getargspec() results. |
---|
318 | grouped |
---|
319 | Defaults to True; include (parens, around, argument) lists |
---|
320 | |
---|
321 | Returns: |
---|
322 | |
---|
323 | args |
---|
324 | Full inspect.formatargspec for fn |
---|
325 | self_arg |
---|
326 | The name of the first positional argument, varargs[0], or None |
---|
327 | if the function defines no positional arguments. |
---|
328 | apply_pos |
---|
329 | args, re-written in calling rather than receiving syntax. Arguments are |
---|
330 | passed positionally. |
---|
331 | apply_kw |
---|
332 | Like apply_pos, except keyword-ish args are passed as keywords. |
---|
333 | |
---|
334 | Example:: |
---|
335 | |
---|
336 | >>> format_argspec_plus(lambda self, a, b, c=3, **d: 123) |
---|
337 | {'args': '(self, a, b, c=3, **d)', |
---|
338 | 'self_arg': 'self', |
---|
339 | 'apply_kw': '(self, a, b, c=c, **d)', |
---|
340 | 'apply_pos': '(self, a, b, c, **d)'} |
---|
341 | |
---|
342 | """ |
---|
343 | spec = callable(fn) and inspect.getargspec(fn) or fn |
---|
344 | args = inspect.formatargspec(*spec) |
---|
345 | if spec[0]: |
---|
346 | self_arg = spec[0][0] |
---|
347 | elif spec[1]: |
---|
348 | self_arg = '%s[0]' % spec[1] |
---|
349 | else: |
---|
350 | self_arg = None |
---|
351 | apply_pos = inspect.formatargspec(spec[0], spec[1], spec[2]) |
---|
352 | defaulted_vals = spec[3] is not None and spec[0][0-len(spec[3]):] or () |
---|
353 | apply_kw = inspect.formatargspec(spec[0], spec[1], spec[2], defaulted_vals, |
---|
354 | formatvalue=lambda x: '=' + x) |
---|
355 | if grouped: |
---|
356 | return dict(args=args, self_arg=self_arg, |
---|
357 | apply_pos=apply_pos, apply_kw=apply_kw) |
---|
358 | else: |
---|
359 | return dict(args=args[1:-1], self_arg=self_arg, |
---|
360 | apply_pos=apply_pos[1:-1], apply_kw=apply_kw[1:-1]) |
---|
361 | |
---|
362 | def format_argspec_init(method, grouped=True): |
---|
363 | """format_argspec_plus with considerations for typical __init__ methods |
---|
364 | |
---|
365 | Wraps format_argspec_plus with error handling strategies for typical |
---|
366 | __init__ cases:: |
---|
367 | |
---|
368 | object.__init__ -> (self) |
---|
369 | other unreflectable (usually C) -> (self, *args, **kwargs) |
---|
370 | |
---|
371 | """ |
---|
372 | try: |
---|
373 | return format_argspec_plus(method, grouped=grouped) |
---|
374 | except TypeError: |
---|
375 | self_arg = 'self' |
---|
376 | if method is object.__init__: |
---|
377 | args = grouped and '(self)' or 'self' |
---|
378 | else: |
---|
379 | args = (grouped and '(self, *args, **kwargs)' |
---|
380 | or 'self, *args, **kwargs') |
---|
381 | return dict(self_arg='self', args=args, apply_pos=args, apply_kw=args) |
---|
382 | |
---|
383 | def getargspec_init(method): |
---|
384 | """inspect.getargspec with considerations for typical __init__ methods |
---|
385 | |
---|
386 | Wraps inspect.getargspec with error handling for typical __init__ cases:: |
---|
387 | |
---|
388 | object.__init__ -> (self) |
---|
389 | other unreflectable (usually C) -> (self, *args, **kwargs) |
---|
390 | |
---|
391 | """ |
---|
392 | try: |
---|
393 | return inspect.getargspec(method) |
---|
394 | except TypeError: |
---|
395 | if method is object.__init__: |
---|
396 | return (['self'], None, None, None) |
---|
397 | else: |
---|
398 | return (['self'], 'args', 'kwargs', None) |
---|
399 | |
---|
400 | |
---|
401 | def unbound_method_to_callable(func_or_cls): |
---|
402 | """Adjust the incoming callable such that a 'self' argument is not required.""" |
---|
403 | |
---|
404 | if isinstance(func_or_cls, types.MethodType) and not func_or_cls.im_self: |
---|
405 | return func_or_cls.im_func |
---|
406 | else: |
---|
407 | return func_or_cls |
---|
408 | |
---|
409 | def class_hierarchy(cls): |
---|
410 | """Return an unordered sequence of all classes related to cls. |
---|
411 | |
---|
412 | Traverses diamond hierarchies. |
---|
413 | |
---|
414 | Fibs slightly: subclasses of builtin types are not returned. Thus |
---|
415 | class_hierarchy(class A(object)) returns (A, object), not A plus every |
---|
416 | class systemwide that derives from object. |
---|
417 | |
---|
418 | Old-style classes are discarded and hierarchies rooted on them |
---|
419 | will not be descended. |
---|
420 | |
---|
421 | """ |
---|
422 | if isinstance(cls, types.ClassType): |
---|
423 | return list() |
---|
424 | hier = set([cls]) |
---|
425 | process = list(cls.__mro__) |
---|
426 | while process: |
---|
427 | c = process.pop() |
---|
428 | if isinstance(c, types.ClassType): |
---|
429 | continue |
---|
430 | for b in (_ for _ in c.__bases__ |
---|
431 | if _ not in hier and not isinstance(_, types.ClassType)): |
---|
432 | process.append(b) |
---|
433 | hier.add(b) |
---|
434 | if c.__module__ == '__builtin__' or not hasattr(c, '__subclasses__'): |
---|
435 | continue |
---|
436 | for s in [_ for _ in c.__subclasses__() if _ not in hier]: |
---|
437 | process.append(s) |
---|
438 | hier.add(s) |
---|
439 | return list(hier) |
---|
440 | |
---|
441 | def iterate_attributes(cls): |
---|
442 | """iterate all the keys and attributes associated with a class, without using getattr(). |
---|
443 | |
---|
444 | Does not use getattr() so that class-sensitive descriptors (i.e. property.__get__()) |
---|
445 | are not called. |
---|
446 | |
---|
447 | """ |
---|
448 | keys = dir(cls) |
---|
449 | for key in keys: |
---|
450 | for c in cls.__mro__: |
---|
451 | if key in c.__dict__: |
---|
452 | yield (key, c.__dict__[key]) |
---|
453 | break |
---|
454 | |
---|
455 | # from paste.deploy.converters |
---|
456 | def asbool(obj): |
---|
457 | if isinstance(obj, (str, unicode)): |
---|
458 | obj = obj.strip().lower() |
---|
459 | if obj in ['true', 'yes', 'on', 'y', 't', '1']: |
---|
460 | return True |
---|
461 | elif obj in ['false', 'no', 'off', 'n', 'f', '0']: |
---|
462 | return False |
---|
463 | else: |
---|
464 | raise ValueError("String is not true/false: %r" % obj) |
---|
465 | return bool(obj) |
---|
466 | |
---|
467 | def coerce_kw_type(kw, key, type_, flexi_bool=True): |
---|
468 | """If 'key' is present in dict 'kw', coerce its value to type 'type\_' if |
---|
469 | necessary. If 'flexi_bool' is True, the string '0' is considered false |
---|
470 | when coercing to boolean. |
---|
471 | """ |
---|
472 | |
---|
473 | if key in kw and type(kw[key]) is not type_ and kw[key] is not None: |
---|
474 | if type_ is bool and flexi_bool: |
---|
475 | kw[key] = asbool(kw[key]) |
---|
476 | else: |
---|
477 | kw[key] = type_(kw[key]) |
---|
478 | |
---|
479 | def duck_type_collection(specimen, default=None): |
---|
480 | """Given an instance or class, guess if it is or is acting as one of |
---|
481 | the basic collection types: list, set and dict. If the __emulates__ |
---|
482 | property is present, return that preferentially. |
---|
483 | """ |
---|
484 | |
---|
485 | if hasattr(specimen, '__emulates__'): |
---|
486 | # canonicalize set vs sets.Set to a standard: the builtin set |
---|
487 | if (specimen.__emulates__ is not None and |
---|
488 | issubclass(specimen.__emulates__, set_types)): |
---|
489 | return set |
---|
490 | else: |
---|
491 | return specimen.__emulates__ |
---|
492 | |
---|
493 | isa = isinstance(specimen, type) and issubclass or isinstance |
---|
494 | if isa(specimen, list): |
---|
495 | return list |
---|
496 | elif isa(specimen, set_types): |
---|
497 | return set |
---|
498 | elif isa(specimen, dict): |
---|
499 | return dict |
---|
500 | |
---|
501 | if hasattr(specimen, 'append'): |
---|
502 | return list |
---|
503 | elif hasattr(specimen, 'add'): |
---|
504 | return set |
---|
505 | elif hasattr(specimen, 'set'): |
---|
506 | return dict |
---|
507 | else: |
---|
508 | return default |
---|
509 | |
---|
510 | def dictlike_iteritems(dictlike): |
---|
511 | """Return a (key, value) iterator for almost any dict-like object.""" |
---|
512 | |
---|
513 | if hasattr(dictlike, 'iteritems'): |
---|
514 | return dictlike.iteritems() |
---|
515 | elif hasattr(dictlike, 'items'): |
---|
516 | return iter(dictlike.items()) |
---|
517 | |
---|
518 | getter = getattr(dictlike, '__getitem__', getattr(dictlike, 'get', None)) |
---|
519 | if getter is None: |
---|
520 | raise TypeError( |
---|
521 | "Object '%r' is not dict-like" % dictlike) |
---|
522 | |
---|
523 | if hasattr(dictlike, 'iterkeys'): |
---|
524 | def iterator(): |
---|
525 | for key in dictlike.iterkeys(): |
---|
526 | yield key, getter(key) |
---|
527 | return iterator() |
---|
528 | elif hasattr(dictlike, 'keys'): |
---|
529 | return iter((key, getter(key)) for key in dictlike.keys()) |
---|
530 | else: |
---|
531 | raise TypeError( |
---|
532 | "Object '%r' is not dict-like" % dictlike) |
---|
533 | |
---|
534 | def assert_arg_type(arg, argtype, name): |
---|
535 | if isinstance(arg, argtype): |
---|
536 | return arg |
---|
537 | else: |
---|
538 | if isinstance(argtype, tuple): |
---|
539 | raise exc.ArgumentError("Argument '%s' is expected to be one of type %s, got '%s'" % (name, ' or '.join("'%s'" % str(a) for a in argtype), str(type(arg)))) |
---|
540 | else: |
---|
541 | raise exc.ArgumentError("Argument '%s' is expected to be of type '%s', got '%s'" % (name, str(argtype), str(type(arg)))) |
---|
542 | |
---|
543 | _creation_order = 1 |
---|
544 | def set_creation_order(instance): |
---|
545 | """Assign a '_creation_order' sequence to the given instance. |
---|
546 | |
---|
547 | This allows multiple instances to be sorted in order of creation |
---|
548 | (typically within a single thread; the counter is not particularly |
---|
549 | threadsafe). |
---|
550 | |
---|
551 | """ |
---|
552 | global _creation_order |
---|
553 | instance._creation_order = _creation_order |
---|
554 | _creation_order +=1 |
---|
555 | |
---|
556 | def warn_exception(func, *args, **kwargs): |
---|
557 | """executes the given function, catches all exceptions and converts to a warning.""" |
---|
558 | try: |
---|
559 | return func(*args, **kwargs) |
---|
560 | except: |
---|
561 | warn("%s('%s') ignored" % sys.exc_info()[0:2]) |
---|
562 | |
---|
563 | def monkeypatch_proxied_specials(into_cls, from_cls, skip=None, only=None, |
---|
564 | name='self.proxy', from_instance=None): |
---|
565 | """Automates delegation of __specials__ for a proxying type.""" |
---|
566 | |
---|
567 | if only: |
---|
568 | dunders = only |
---|
569 | else: |
---|
570 | if skip is None: |
---|
571 | skip = ('__slots__', '__del__', '__getattribute__', |
---|
572 | '__metaclass__', '__getstate__', '__setstate__') |
---|
573 | dunders = [m for m in dir(from_cls) |
---|
574 | if (m.startswith('__') and m.endswith('__') and |
---|
575 | not hasattr(into_cls, m) and m not in skip)] |
---|
576 | for method in dunders: |
---|
577 | try: |
---|
578 | fn = getattr(from_cls, method) |
---|
579 | if not hasattr(fn, '__call__'): |
---|
580 | continue |
---|
581 | fn = getattr(fn, 'im_func', fn) |
---|
582 | except AttributeError: |
---|
583 | continue |
---|
584 | try: |
---|
585 | spec = inspect.getargspec(fn) |
---|
586 | fn_args = inspect.formatargspec(spec[0]) |
---|
587 | d_args = inspect.formatargspec(spec[0][1:]) |
---|
588 | except TypeError: |
---|
589 | fn_args = '(self, *args, **kw)' |
---|
590 | d_args = '(*args, **kw)' |
---|
591 | |
---|
592 | py = ("def %(method)s%(fn_args)s: " |
---|
593 | "return %(name)s.%(method)s%(d_args)s" % locals()) |
---|
594 | |
---|
595 | env = from_instance is not None and {name: from_instance} or {} |
---|
596 | exec py in env |
---|
597 | try: |
---|
598 | env[method].func_defaults = fn.func_defaults |
---|
599 | except AttributeError: |
---|
600 | pass |
---|
601 | setattr(into_cls, method, env[method]) |
---|
602 | |
---|
603 | |
---|
604 | class OrderedProperties(object): |
---|
605 | """An object that maintains the order in which attributes are set upon it. |
---|
606 | |
---|
607 | Also provides an iterator and a very basic getitem/setitem |
---|
608 | interface to those attributes. |
---|
609 | |
---|
610 | (Not really a dict, since it iterates over values, not keys. Not really |
---|
611 | a list, either, since each value must have a key associated; hence there is |
---|
612 | no append or extend.) |
---|
613 | """ |
---|
614 | |
---|
615 | def __init__(self): |
---|
616 | self.__dict__['_data'] = OrderedDict() |
---|
617 | |
---|
618 | def __len__(self): |
---|
619 | return len(self._data) |
---|
620 | |
---|
621 | def __iter__(self): |
---|
622 | return self._data.itervalues() |
---|
623 | |
---|
624 | def __add__(self, other): |
---|
625 | return list(self) + list(other) |
---|
626 | |
---|
627 | def __setitem__(self, key, object): |
---|
628 | self._data[key] = object |
---|
629 | |
---|
630 | def __getitem__(self, key): |
---|
631 | return self._data[key] |
---|
632 | |
---|
633 | def __delitem__(self, key): |
---|
634 | del self._data[key] |
---|
635 | |
---|
636 | def __setattr__(self, key, object): |
---|
637 | self._data[key] = object |
---|
638 | |
---|
639 | def __getstate__(self): |
---|
640 | return {'_data': self.__dict__['_data']} |
---|
641 | |
---|
642 | def __setstate__(self, state): |
---|
643 | self.__dict__['_data'] = state['_data'] |
---|
644 | |
---|
645 | def __getattr__(self, key): |
---|
646 | try: |
---|
647 | return self._data[key] |
---|
648 | except KeyError: |
---|
649 | raise AttributeError(key) |
---|
650 | |
---|
651 | def __contains__(self, key): |
---|
652 | return key in self._data |
---|
653 | |
---|
654 | def update(self, value): |
---|
655 | self._data.update(value) |
---|
656 | |
---|
657 | def get(self, key, default=None): |
---|
658 | if key in self: |
---|
659 | return self[key] |
---|
660 | else: |
---|
661 | return default |
---|
662 | |
---|
663 | def keys(self): |
---|
664 | return self._data.keys() |
---|
665 | |
---|
666 | def has_key(self, key): |
---|
667 | return self._data.has_key(key) |
---|
668 | |
---|
669 | def clear(self): |
---|
670 | self._data.clear() |
---|
671 | |
---|
672 | |
---|
673 | class OrderedDict(dict): |
---|
674 | """A dict that returns keys/values/items in the order they were added.""" |
---|
675 | |
---|
676 | def __init__(self, ____sequence=None, **kwargs): |
---|
677 | self._list = [] |
---|
678 | if ____sequence is None: |
---|
679 | if kwargs: |
---|
680 | self.update(**kwargs) |
---|
681 | else: |
---|
682 | self.update(____sequence, **kwargs) |
---|
683 | |
---|
684 | def clear(self): |
---|
685 | self._list = [] |
---|
686 | dict.clear(self) |
---|
687 | |
---|
688 | def copy(self): |
---|
689 | return self.__copy__() |
---|
690 | |
---|
691 | def __copy__(self): |
---|
692 | return OrderedDict(self) |
---|
693 | |
---|
694 | def sort(self, *arg, **kw): |
---|
695 | self._list.sort(*arg, **kw) |
---|
696 | |
---|
697 | def update(self, ____sequence=None, **kwargs): |
---|
698 | if ____sequence is not None: |
---|
699 | if hasattr(____sequence, 'keys'): |
---|
700 | for key in ____sequence.keys(): |
---|
701 | self.__setitem__(key, ____sequence[key]) |
---|
702 | else: |
---|
703 | for key, value in ____sequence: |
---|
704 | self[key] = value |
---|
705 | if kwargs: |
---|
706 | self.update(kwargs) |
---|
707 | |
---|
708 | def setdefault(self, key, value): |
---|
709 | if key not in self: |
---|
710 | self.__setitem__(key, value) |
---|
711 | return value |
---|
712 | else: |
---|
713 | return self.__getitem__(key) |
---|
714 | |
---|
715 | def __iter__(self): |
---|
716 | return iter(self._list) |
---|
717 | |
---|
718 | def values(self): |
---|
719 | return [self[key] for key in self._list] |
---|
720 | |
---|
721 | def itervalues(self): |
---|
722 | return iter(self.values()) |
---|
723 | |
---|
724 | def keys(self): |
---|
725 | return list(self._list) |
---|
726 | |
---|
727 | def iterkeys(self): |
---|
728 | return iter(self.keys()) |
---|
729 | |
---|
730 | def items(self): |
---|
731 | return [(key, self[key]) for key in self.keys()] |
---|
732 | |
---|
733 | def iteritems(self): |
---|
734 | return iter(self.items()) |
---|
735 | |
---|
736 | def __setitem__(self, key, object): |
---|
737 | if key not in self: |
---|
738 | self._list.append(key) |
---|
739 | dict.__setitem__(self, key, object) |
---|
740 | |
---|
741 | def __delitem__(self, key): |
---|
742 | dict.__delitem__(self, key) |
---|
743 | self._list.remove(key) |
---|
744 | |
---|
745 | def pop(self, key, *default): |
---|
746 | present = key in self |
---|
747 | value = dict.pop(self, key, *default) |
---|
748 | if present: |
---|
749 | self._list.remove(key) |
---|
750 | return value |
---|
751 | |
---|
752 | def popitem(self): |
---|
753 | item = dict.popitem(self) |
---|
754 | self._list.remove(item[0]) |
---|
755 | return item |
---|
756 | |
---|
757 | class OrderedSet(set): |
---|
758 | def __init__(self, d=None): |
---|
759 | set.__init__(self) |
---|
760 | self._list = [] |
---|
761 | if d is not None: |
---|
762 | self.update(d) |
---|
763 | |
---|
764 | def add(self, element): |
---|
765 | if element not in self: |
---|
766 | self._list.append(element) |
---|
767 | set.add(self, element) |
---|
768 | |
---|
769 | def remove(self, element): |
---|
770 | set.remove(self, element) |
---|
771 | self._list.remove(element) |
---|
772 | |
---|
773 | def insert(self, pos, element): |
---|
774 | if element not in self: |
---|
775 | self._list.insert(pos, element) |
---|
776 | set.add(self, element) |
---|
777 | |
---|
778 | def discard(self, element): |
---|
779 | if element in self: |
---|
780 | self._list.remove(element) |
---|
781 | set.remove(self, element) |
---|
782 | |
---|
783 | def clear(self): |
---|
784 | set.clear(self) |
---|
785 | self._list = [] |
---|
786 | |
---|
787 | def __getitem__(self, key): |
---|
788 | return self._list[key] |
---|
789 | |
---|
790 | def __iter__(self): |
---|
791 | return iter(self._list) |
---|
792 | |
---|
793 | def __repr__(self): |
---|
794 | return '%s(%r)' % (self.__class__.__name__, self._list) |
---|
795 | |
---|
796 | __str__ = __repr__ |
---|
797 | |
---|
798 | def update(self, iterable): |
---|
799 | add = self.add |
---|
800 | for i in iterable: |
---|
801 | add(i) |
---|
802 | return self |
---|
803 | |
---|
804 | __ior__ = update |
---|
805 | |
---|
806 | def union(self, other): |
---|
807 | result = self.__class__(self) |
---|
808 | result.update(other) |
---|
809 | return result |
---|
810 | |
---|
811 | __or__ = union |
---|
812 | |
---|
813 | def intersection(self, other): |
---|
814 | other = set(other) |
---|
815 | return self.__class__(a for a in self if a in other) |
---|
816 | |
---|
817 | __and__ = intersection |
---|
818 | |
---|
819 | def symmetric_difference(self, other): |
---|
820 | other = set(other) |
---|
821 | result = self.__class__(a for a in self if a not in other) |
---|
822 | result.update(a for a in other if a not in self) |
---|
823 | return result |
---|
824 | |
---|
825 | __xor__ = symmetric_difference |
---|
826 | |
---|
827 | def difference(self, other): |
---|
828 | other = set(other) |
---|
829 | return self.__class__(a for a in self if a not in other) |
---|
830 | |
---|
831 | __sub__ = difference |
---|
832 | |
---|
833 | def intersection_update(self, other): |
---|
834 | other = set(other) |
---|
835 | set.intersection_update(self, other) |
---|
836 | self._list = [ a for a in self._list if a in other] |
---|
837 | return self |
---|
838 | |
---|
839 | __iand__ = intersection_update |
---|
840 | |
---|
841 | def symmetric_difference_update(self, other): |
---|
842 | set.symmetric_difference_update(self, other) |
---|
843 | self._list = [ a for a in self._list if a in self] |
---|
844 | self._list += [ a for a in other._list if a in self] |
---|
845 | return self |
---|
846 | |
---|
847 | __ixor__ = symmetric_difference_update |
---|
848 | |
---|
849 | def difference_update(self, other): |
---|
850 | set.difference_update(self, other) |
---|
851 | self._list = [ a for a in self._list if a in self] |
---|
852 | return self |
---|
853 | |
---|
854 | __isub__ = difference_update |
---|
855 | |
---|
856 | |
---|
857 | class IdentitySet(object): |
---|
858 | """A set that considers only object id() for uniqueness. |
---|
859 | |
---|
860 | This strategy has edge cases for builtin types- it's possible to have |
---|
861 | two 'foo' strings in one of these sets, for example. Use sparingly. |
---|
862 | |
---|
863 | """ |
---|
864 | |
---|
865 | _working_set = set |
---|
866 | |
---|
867 | def __init__(self, iterable=None): |
---|
868 | self._members = dict() |
---|
869 | if iterable: |
---|
870 | for o in iterable: |
---|
871 | self.add(o) |
---|
872 | |
---|
873 | def add(self, value): |
---|
874 | self._members[id(value)] = value |
---|
875 | |
---|
876 | def __contains__(self, value): |
---|
877 | return id(value) in self._members |
---|
878 | |
---|
879 | def remove(self, value): |
---|
880 | del self._members[id(value)] |
---|
881 | |
---|
882 | def discard(self, value): |
---|
883 | try: |
---|
884 | self.remove(value) |
---|
885 | except KeyError: |
---|
886 | pass |
---|
887 | |
---|
888 | def pop(self): |
---|
889 | try: |
---|
890 | pair = self._members.popitem() |
---|
891 | return pair[1] |
---|
892 | except KeyError: |
---|
893 | raise KeyError('pop from an empty set') |
---|
894 | |
---|
895 | def clear(self): |
---|
896 | self._members.clear() |
---|
897 | |
---|
898 | def __cmp__(self, other): |
---|
899 | raise TypeError('cannot compare sets using cmp()') |
---|
900 | |
---|
901 | def __eq__(self, other): |
---|
902 | if isinstance(other, IdentitySet): |
---|
903 | return self._members == other._members |
---|
904 | else: |
---|
905 | return False |
---|
906 | |
---|
907 | def __ne__(self, other): |
---|
908 | if isinstance(other, IdentitySet): |
---|
909 | return self._members != other._members |
---|
910 | else: |
---|
911 | return True |
---|
912 | |
---|
913 | def issubset(self, iterable): |
---|
914 | other = type(self)(iterable) |
---|
915 | |
---|
916 | if len(self) > len(other): |
---|
917 | return False |
---|
918 | for m in itertools.ifilterfalse(other._members.has_key, |
---|
919 | self._members.iterkeys()): |
---|
920 | return False |
---|
921 | return True |
---|
922 | |
---|
923 | def __le__(self, other): |
---|
924 | if not isinstance(other, IdentitySet): |
---|
925 | return NotImplemented |
---|
926 | return self.issubset(other) |
---|
927 | |
---|
928 | def __lt__(self, other): |
---|
929 | if not isinstance(other, IdentitySet): |
---|
930 | return NotImplemented |
---|
931 | return len(self) < len(other) and self.issubset(other) |
---|
932 | |
---|
933 | def issuperset(self, iterable): |
---|
934 | other = type(self)(iterable) |
---|
935 | |
---|
936 | if len(self) < len(other): |
---|
937 | return False |
---|
938 | |
---|
939 | for m in itertools.ifilterfalse(self._members.has_key, |
---|
940 | other._members.iterkeys()): |
---|
941 | return False |
---|
942 | return True |
---|
943 | |
---|
944 | def __ge__(self, other): |
---|
945 | if not isinstance(other, IdentitySet): |
---|
946 | return NotImplemented |
---|
947 | return self.issuperset(other) |
---|
948 | |
---|
949 | def __gt__(self, other): |
---|
950 | if not isinstance(other, IdentitySet): |
---|
951 | return NotImplemented |
---|
952 | return len(self) > len(other) and self.issuperset(other) |
---|
953 | |
---|
954 | def union(self, iterable): |
---|
955 | result = type(self)() |
---|
956 | # testlib.pragma exempt:__hash__ |
---|
957 | result._members.update( |
---|
958 | self._working_set(self._member_id_tuples()).union(_iter_id(iterable))) |
---|
959 | return result |
---|
960 | |
---|
961 | def __or__(self, other): |
---|
962 | if not isinstance(other, IdentitySet): |
---|
963 | return NotImplemented |
---|
964 | return self.union(other) |
---|
965 | |
---|
966 | def update(self, iterable): |
---|
967 | self._members = self.union(iterable)._members |
---|
968 | |
---|
969 | def __ior__(self, other): |
---|
970 | if not isinstance(other, IdentitySet): |
---|
971 | return NotImplemented |
---|
972 | self.update(other) |
---|
973 | return self |
---|
974 | |
---|
975 | def difference(self, iterable): |
---|
976 | result = type(self)() |
---|
977 | # testlib.pragma exempt:__hash__ |
---|
978 | result._members.update( |
---|
979 | self._working_set(self._member_id_tuples()).difference(_iter_id(iterable))) |
---|
980 | return result |
---|
981 | |
---|
982 | def __sub__(self, other): |
---|
983 | if not isinstance(other, IdentitySet): |
---|
984 | return NotImplemented |
---|
985 | return self.difference(other) |
---|
986 | |
---|
987 | def difference_update(self, iterable): |
---|
988 | self._members = self.difference(iterable)._members |
---|
989 | |
---|
990 | def __isub__(self, other): |
---|
991 | if not isinstance(other, IdentitySet): |
---|
992 | return NotImplemented |
---|
993 | self.difference_update(other) |
---|
994 | return self |
---|
995 | |
---|
996 | def intersection(self, iterable): |
---|
997 | result = type(self)() |
---|
998 | # testlib.pragma exempt:__hash__ |
---|
999 | result._members.update( |
---|
1000 | self._working_set(self._member_id_tuples()).intersection(_iter_id(iterable))) |
---|
1001 | return result |
---|
1002 | |
---|
1003 | def __and__(self, other): |
---|
1004 | if not isinstance(other, IdentitySet): |
---|
1005 | return NotImplemented |
---|
1006 | return self.intersection(other) |
---|
1007 | |
---|
1008 | def intersection_update(self, iterable): |
---|
1009 | self._members = self.intersection(iterable)._members |
---|
1010 | |
---|
1011 | def __iand__(self, other): |
---|
1012 | if not isinstance(other, IdentitySet): |
---|
1013 | return NotImplemented |
---|
1014 | self.intersection_update(other) |
---|
1015 | return self |
---|
1016 | |
---|
1017 | def symmetric_difference(self, iterable): |
---|
1018 | result = type(self)() |
---|
1019 | # testlib.pragma exempt:__hash__ |
---|
1020 | result._members.update( |
---|
1021 | self._working_set(self._member_id_tuples()).symmetric_difference(_iter_id(iterable))) |
---|
1022 | return result |
---|
1023 | |
---|
1024 | def _member_id_tuples(self): |
---|
1025 | return ((id(v), v) for v in self._members.itervalues()) |
---|
1026 | |
---|
1027 | def __xor__(self, other): |
---|
1028 | if not isinstance(other, IdentitySet): |
---|
1029 | return NotImplemented |
---|
1030 | return self.symmetric_difference(other) |
---|
1031 | |
---|
1032 | def symmetric_difference_update(self, iterable): |
---|
1033 | self._members = self.symmetric_difference(iterable)._members |
---|
1034 | |
---|
1035 | def __ixor__(self, other): |
---|
1036 | if not isinstance(other, IdentitySet): |
---|
1037 | return NotImplemented |
---|
1038 | self.symmetric_difference(other) |
---|
1039 | return self |
---|
1040 | |
---|
1041 | def copy(self): |
---|
1042 | return type(self)(self._members.itervalues()) |
---|
1043 | |
---|
1044 | __copy__ = copy |
---|
1045 | |
---|
1046 | def __len__(self): |
---|
1047 | return len(self._members) |
---|
1048 | |
---|
1049 | def __iter__(self): |
---|
1050 | return self._members.itervalues() |
---|
1051 | |
---|
1052 | def __hash__(self): |
---|
1053 | raise TypeError('set objects are unhashable') |
---|
1054 | |
---|
1055 | def __repr__(self): |
---|
1056 | return '%s(%r)' % (type(self).__name__, self._members.values()) |
---|
1057 | |
---|
1058 | |
---|
1059 | class OrderedIdentitySet(IdentitySet): |
---|
1060 | class _working_set(OrderedSet): |
---|
1061 | # a testing pragma: exempt the OIDS working set from the test suite's |
---|
1062 | # "never call the user's __hash__" assertions. this is a big hammer, |
---|
1063 | # but it's safe here: IDS operates on (id, instance) tuples in the |
---|
1064 | # working set. |
---|
1065 | __sa_hash_exempt__ = True |
---|
1066 | |
---|
1067 | def __init__(self, iterable=None): |
---|
1068 | IdentitySet.__init__(self) |
---|
1069 | self._members = OrderedDict() |
---|
1070 | if iterable: |
---|
1071 | for o in iterable: |
---|
1072 | self.add(o) |
---|
1073 | |
---|
1074 | def _iter_id(iterable): |
---|
1075 | """Generator: ((id(o), o) for o in iterable).""" |
---|
1076 | |
---|
1077 | for item in iterable: |
---|
1078 | yield id(item), item |
---|
1079 | |
---|
1080 | # define collections that are capable of storing |
---|
1081 | # ColumnElement objects as hashable keys/elements. |
---|
1082 | column_set = set |
---|
1083 | column_dict = dict |
---|
1084 | ordered_column_set = OrderedSet |
---|
1085 | populate_column_dict = PopulateDict |
---|
1086 | |
---|
1087 | def unique_list(seq, compare_with=set): |
---|
1088 | seen = compare_with() |
---|
1089 | return [x for x in seq if x not in seen and not seen.add(x)] |
---|
1090 | |
---|
1091 | class UniqueAppender(object): |
---|
1092 | """Appends items to a collection ensuring uniqueness. |
---|
1093 | |
---|
1094 | Additional appends() of the same object are ignored. Membership is |
---|
1095 | determined by identity (``is a``) not equality (``==``). |
---|
1096 | """ |
---|
1097 | |
---|
1098 | def __init__(self, data, via=None): |
---|
1099 | self.data = data |
---|
1100 | self._unique = IdentitySet() |
---|
1101 | if via: |
---|
1102 | self._data_appender = getattr(data, via) |
---|
1103 | elif hasattr(data, 'append'): |
---|
1104 | self._data_appender = data.append |
---|
1105 | elif hasattr(data, 'add'): |
---|
1106 | # TODO: we think its a set here. bypass unneeded uniquing logic ? |
---|
1107 | self._data_appender = data.add |
---|
1108 | |
---|
1109 | def append(self, item): |
---|
1110 | if item not in self._unique: |
---|
1111 | self._data_appender(item) |
---|
1112 | self._unique.add(item) |
---|
1113 | |
---|
1114 | def __iter__(self): |
---|
1115 | return iter(self.data) |
---|
1116 | |
---|
1117 | |
---|
1118 | class ScopedRegistry(object): |
---|
1119 | """A Registry that can store one or multiple instances of a single |
---|
1120 | class on a per-thread scoped basis, or on a customized scope. |
---|
1121 | |
---|
1122 | createfunc |
---|
1123 | a callable that returns a new object to be placed in the registry |
---|
1124 | |
---|
1125 | scopefunc |
---|
1126 | a callable that will return a key to store/retrieve an object. |
---|
1127 | """ |
---|
1128 | |
---|
1129 | def __init__(self, createfunc, scopefunc): |
---|
1130 | self.createfunc = createfunc |
---|
1131 | self.scopefunc = scopefunc |
---|
1132 | self.registry = {} |
---|
1133 | |
---|
1134 | def __call__(self): |
---|
1135 | key = self.scopefunc() |
---|
1136 | try: |
---|
1137 | return self.registry[key] |
---|
1138 | except KeyError: |
---|
1139 | return self.registry.setdefault(key, self.createfunc()) |
---|
1140 | |
---|
1141 | def has(self): |
---|
1142 | return self.scopefunc() in self.registry |
---|
1143 | |
---|
1144 | def set(self, obj): |
---|
1145 | self.registry[self.scopefunc()] = obj |
---|
1146 | |
---|
1147 | def clear(self): |
---|
1148 | try: |
---|
1149 | del self.registry[self.scopefunc()] |
---|
1150 | except KeyError: |
---|
1151 | pass |
---|
1152 | |
---|
1153 | class ThreadLocalRegistry(ScopedRegistry): |
---|
1154 | def __init__(self, createfunc): |
---|
1155 | self.createfunc = createfunc |
---|
1156 | self.registry = threading.local() |
---|
1157 | |
---|
1158 | def __call__(self): |
---|
1159 | try: |
---|
1160 | return self.registry.value |
---|
1161 | except AttributeError: |
---|
1162 | val = self.registry.value = self.createfunc() |
---|
1163 | return val |
---|
1164 | |
---|
1165 | def has(self): |
---|
1166 | return hasattr(self.registry, "value") |
---|
1167 | |
---|
1168 | def set(self, obj): |
---|
1169 | self.registry.value = obj |
---|
1170 | |
---|
1171 | def clear(self): |
---|
1172 | try: |
---|
1173 | del self.registry.value |
---|
1174 | except AttributeError: |
---|
1175 | pass |
---|
1176 | |
---|
1177 | class _symbol(object): |
---|
1178 | def __init__(self, name): |
---|
1179 | """Construct a new named symbol.""" |
---|
1180 | assert isinstance(name, str) |
---|
1181 | self.name = name |
---|
1182 | def __reduce__(self): |
---|
1183 | return symbol, (self.name,) |
---|
1184 | def __repr__(self): |
---|
1185 | return "<symbol '%s>" % self.name |
---|
1186 | _symbol.__name__ = 'symbol' |
---|
1187 | |
---|
1188 | |
---|
1189 | class symbol(object): |
---|
1190 | """A constant symbol. |
---|
1191 | |
---|
1192 | >>> symbol('foo') is symbol('foo') |
---|
1193 | True |
---|
1194 | >>> symbol('foo') |
---|
1195 | <symbol 'foo> |
---|
1196 | |
---|
1197 | A slight refinement of the MAGICCOOKIE=object() pattern. The primary |
---|
1198 | advantage of symbol() is its repr(). They are also singletons. |
---|
1199 | |
---|
1200 | Repeated calls of symbol('name') will all return the same instance. |
---|
1201 | |
---|
1202 | """ |
---|
1203 | symbols = {} |
---|
1204 | _lock = threading.Lock() |
---|
1205 | |
---|
1206 | def __new__(cls, name): |
---|
1207 | cls._lock.acquire() |
---|
1208 | try: |
---|
1209 | sym = cls.symbols.get(name) |
---|
1210 | if sym is None: |
---|
1211 | cls.symbols[name] = sym = _symbol(name) |
---|
1212 | return sym |
---|
1213 | finally: |
---|
1214 | symbol._lock.release() |
---|
1215 | |
---|
1216 | |
---|
1217 | def as_interface(obj, cls=None, methods=None, required=None): |
---|
1218 | """Ensure basic interface compliance for an instance or dict of callables. |
---|
1219 | |
---|
1220 | Checks that ``obj`` implements public methods of ``cls`` or has members |
---|
1221 | listed in ``methods``. If ``required`` is not supplied, implementing at |
---|
1222 | least one interface method is sufficient. Methods present on ``obj`` that |
---|
1223 | are not in the interface are ignored. |
---|
1224 | |
---|
1225 | If ``obj`` is a dict and ``dict`` does not meet the interface |
---|
1226 | requirements, the keys of the dictionary are inspected. Keys present in |
---|
1227 | ``obj`` that are not in the interface will raise TypeErrors. |
---|
1228 | |
---|
1229 | Raises TypeError if ``obj`` does not meet the interface criteria. |
---|
1230 | |
---|
1231 | In all passing cases, an object with callable members is returned. In the |
---|
1232 | simple case, ``obj`` is returned as-is; if dict processing kicks in then |
---|
1233 | an anonymous class is returned. |
---|
1234 | |
---|
1235 | obj |
---|
1236 | A type, instance, or dictionary of callables. |
---|
1237 | cls |
---|
1238 | Optional, a type. All public methods of cls are considered the |
---|
1239 | interface. An ``obj`` instance of cls will always pass, ignoring |
---|
1240 | ``required``.. |
---|
1241 | methods |
---|
1242 | Optional, a sequence of method names to consider as the interface. |
---|
1243 | required |
---|
1244 | Optional, a sequence of mandatory implementations. If omitted, an |
---|
1245 | ``obj`` that provides at least one interface method is considered |
---|
1246 | sufficient. As a convenience, required may be a type, in which case |
---|
1247 | all public methods of the type are required. |
---|
1248 | |
---|
1249 | """ |
---|
1250 | if not cls and not methods: |
---|
1251 | raise TypeError('a class or collection of method names are required') |
---|
1252 | |
---|
1253 | if isinstance(cls, type) and isinstance(obj, cls): |
---|
1254 | return obj |
---|
1255 | |
---|
1256 | interface = set(methods or [m for m in dir(cls) if not m.startswith('_')]) |
---|
1257 | implemented = set(dir(obj)) |
---|
1258 | |
---|
1259 | complies = operator.ge |
---|
1260 | if isinstance(required, type): |
---|
1261 | required = interface |
---|
1262 | elif not required: |
---|
1263 | required = set() |
---|
1264 | complies = operator.gt |
---|
1265 | else: |
---|
1266 | required = set(required) |
---|
1267 | |
---|
1268 | if complies(implemented.intersection(interface), required): |
---|
1269 | return obj |
---|
1270 | |
---|
1271 | # No dict duck typing here. |
---|
1272 | if not type(obj) is dict: |
---|
1273 | qualifier = complies is operator.gt and 'any of' or 'all of' |
---|
1274 | raise TypeError("%r does not implement %s: %s" % ( |
---|
1275 | obj, qualifier, ', '.join(interface))) |
---|
1276 | |
---|
1277 | class AnonymousInterface(object): |
---|
1278 | """A callable-holding shell.""" |
---|
1279 | |
---|
1280 | if cls: |
---|
1281 | AnonymousInterface.__name__ = 'Anonymous' + cls.__name__ |
---|
1282 | found = set() |
---|
1283 | |
---|
1284 | for method, impl in dictlike_iteritems(obj): |
---|
1285 | if method not in interface: |
---|
1286 | raise TypeError("%r: unknown in this interface" % method) |
---|
1287 | if not callable(impl): |
---|
1288 | raise TypeError("%r=%r is not callable" % (method, impl)) |
---|
1289 | setattr(AnonymousInterface, method, staticmethod(impl)) |
---|
1290 | found.add(method) |
---|
1291 | |
---|
1292 | if complies(found, required): |
---|
1293 | return AnonymousInterface |
---|
1294 | |
---|
1295 | raise TypeError("dictionary does not contain required keys %s" % |
---|
1296 | ', '.join(required - found)) |
---|
1297 | |
---|
1298 | def function_named(fn, name): |
---|
1299 | """Return a function with a given __name__. |
---|
1300 | |
---|
1301 | Will assign to __name__ and return the original function if possible on |
---|
1302 | the Python implementation, otherwise a new function will be constructed. |
---|
1303 | |
---|
1304 | """ |
---|
1305 | try: |
---|
1306 | fn.__name__ = name |
---|
1307 | except TypeError: |
---|
1308 | fn = types.FunctionType(fn.func_code, fn.func_globals, name, |
---|
1309 | fn.func_defaults, fn.func_closure) |
---|
1310 | return fn |
---|
1311 | |
---|
1312 | class memoized_property(object): |
---|
1313 | """A read-only @property that is only evaluated once.""" |
---|
1314 | def __init__(self, fget, doc=None): |
---|
1315 | self.fget = fget |
---|
1316 | self.__doc__ = doc or fget.__doc__ |
---|
1317 | self.__name__ = fget.__name__ |
---|
1318 | |
---|
1319 | def __get__(self, obj, cls): |
---|
1320 | if obj is None: |
---|
1321 | return None |
---|
1322 | obj.__dict__[self.__name__] = result = self.fget(obj) |
---|
1323 | return result |
---|
1324 | |
---|
1325 | |
---|
1326 | class memoized_instancemethod(object): |
---|
1327 | """Decorate a method memoize its return value. |
---|
1328 | |
---|
1329 | Best applied to no-arg methods: memoization is not sensitive to |
---|
1330 | argument values, and will always return the same value even when |
---|
1331 | called with different arguments. |
---|
1332 | |
---|
1333 | """ |
---|
1334 | def __init__(self, fget, doc=None): |
---|
1335 | self.fget = fget |
---|
1336 | self.__doc__ = doc or fget.__doc__ |
---|
1337 | self.__name__ = fget.__name__ |
---|
1338 | |
---|
1339 | def __get__(self, obj, cls): |
---|
1340 | if obj is None: |
---|
1341 | return None |
---|
1342 | def oneshot(*args, **kw): |
---|
1343 | result = self.fget(obj, *args, **kw) |
---|
1344 | memo = lambda *a, **kw: result |
---|
1345 | memo.__name__ = self.__name__ |
---|
1346 | memo.__doc__ = self.__doc__ |
---|
1347 | obj.__dict__[self.__name__] = memo |
---|
1348 | return result |
---|
1349 | oneshot.__name__ = self.__name__ |
---|
1350 | oneshot.__doc__ = self.__doc__ |
---|
1351 | return oneshot |
---|
1352 | |
---|
1353 | def reset_memoized(instance, name): |
---|
1354 | instance.__dict__.pop(name, None) |
---|
1355 | |
---|
1356 | class WeakIdentityMapping(weakref.WeakKeyDictionary): |
---|
1357 | """A WeakKeyDictionary with an object identity index. |
---|
1358 | |
---|
1359 | Adds a .by_id dictionary to a regular WeakKeyDictionary. Trades |
---|
1360 | performance during mutation operations for accelerated lookups by id(). |
---|
1361 | |
---|
1362 | The usual cautions about weak dictionaries and iteration also apply to |
---|
1363 | this subclass. |
---|
1364 | |
---|
1365 | """ |
---|
1366 | _none = symbol('none') |
---|
1367 | |
---|
1368 | def __init__(self): |
---|
1369 | weakref.WeakKeyDictionary.__init__(self) |
---|
1370 | self.by_id = {} |
---|
1371 | self._weakrefs = {} |
---|
1372 | |
---|
1373 | def __setitem__(self, object, value): |
---|
1374 | oid = id(object) |
---|
1375 | self.by_id[oid] = value |
---|
1376 | if oid not in self._weakrefs: |
---|
1377 | self._weakrefs[oid] = self._ref(object) |
---|
1378 | weakref.WeakKeyDictionary.__setitem__(self, object, value) |
---|
1379 | |
---|
1380 | def __delitem__(self, object): |
---|
1381 | del self._weakrefs[id(object)] |
---|
1382 | del self.by_id[id(object)] |
---|
1383 | weakref.WeakKeyDictionary.__delitem__(self, object) |
---|
1384 | |
---|
1385 | def setdefault(self, object, default=None): |
---|
1386 | value = weakref.WeakKeyDictionary.setdefault(self, object, default) |
---|
1387 | oid = id(object) |
---|
1388 | if value is default: |
---|
1389 | self.by_id[oid] = default |
---|
1390 | if oid not in self._weakrefs: |
---|
1391 | self._weakrefs[oid] = self._ref(object) |
---|
1392 | return value |
---|
1393 | |
---|
1394 | def pop(self, object, default=_none): |
---|
1395 | if default is self._none: |
---|
1396 | value = weakref.WeakKeyDictionary.pop(self, object) |
---|
1397 | else: |
---|
1398 | value = weakref.WeakKeyDictionary.pop(self, object, default) |
---|
1399 | if id(object) in self.by_id: |
---|
1400 | del self._weakrefs[id(object)] |
---|
1401 | del self.by_id[id(object)] |
---|
1402 | return value |
---|
1403 | |
---|
1404 | def popitem(self): |
---|
1405 | item = weakref.WeakKeyDictionary.popitem(self) |
---|
1406 | oid = id(item[0]) |
---|
1407 | del self._weakrefs[oid] |
---|
1408 | del self.by_id[oid] |
---|
1409 | return item |
---|
1410 | |
---|
1411 | def clear(self): |
---|
1412 | self._weakrefs.clear() |
---|
1413 | self.by_id.clear() |
---|
1414 | weakref.WeakKeyDictionary.clear(self) |
---|
1415 | |
---|
1416 | def update(self, *a, **kw): |
---|
1417 | raise NotImplementedError |
---|
1418 | |
---|
1419 | def _cleanup(self, wr, key=None): |
---|
1420 | if key is None: |
---|
1421 | key = wr.key |
---|
1422 | try: |
---|
1423 | del self._weakrefs[key] |
---|
1424 | except (KeyError, AttributeError): # pragma: no cover |
---|
1425 | pass # pragma: no cover |
---|
1426 | try: |
---|
1427 | del self.by_id[key] |
---|
1428 | except (KeyError, AttributeError): # pragma: no cover |
---|
1429 | pass # pragma: no cover |
---|
1430 | |
---|
1431 | class _keyed_weakref(weakref.ref): |
---|
1432 | def __init__(self, object, callback): |
---|
1433 | weakref.ref.__init__(self, object, callback) |
---|
1434 | self.key = id(object) |
---|
1435 | |
---|
1436 | def _ref(self, object): |
---|
1437 | return self._keyed_weakref(object, self._cleanup) |
---|
1438 | |
---|
1439 | |
---|
1440 | def warn(msg): |
---|
1441 | if isinstance(msg, basestring): |
---|
1442 | warnings.warn(msg, exc.SAWarning, stacklevel=3) |
---|
1443 | else: |
---|
1444 | warnings.warn(msg, stacklevel=3) |
---|
1445 | |
---|
1446 | def warn_deprecated(msg): |
---|
1447 | warnings.warn(msg, exc.SADeprecationWarning, stacklevel=3) |
---|
1448 | |
---|
1449 | def warn_pending_deprecation(msg): |
---|
1450 | warnings.warn(msg, exc.SAPendingDeprecationWarning, stacklevel=3) |
---|
1451 | |
---|
1452 | def deprecated(message=None, add_deprecation_to_docstring=True): |
---|
1453 | """Decorates a function and issues a deprecation warning on use. |
---|
1454 | |
---|
1455 | message |
---|
1456 | If provided, issue message in the warning. A sensible default |
---|
1457 | is used if not provided. |
---|
1458 | |
---|
1459 | add_deprecation_to_docstring |
---|
1460 | Default True. If False, the wrapped function's __doc__ is left |
---|
1461 | as-is. If True, the 'message' is prepended to the docs if |
---|
1462 | provided, or sensible default if message is omitted. |
---|
1463 | """ |
---|
1464 | |
---|
1465 | if add_deprecation_to_docstring: |
---|
1466 | header = message is not None and message or 'Deprecated.' |
---|
1467 | else: |
---|
1468 | header = None |
---|
1469 | |
---|
1470 | if message is None: |
---|
1471 | message = "Call to deprecated function %(func)s" |
---|
1472 | |
---|
1473 | def decorate(fn): |
---|
1474 | return _decorate_with_warning( |
---|
1475 | fn, exc.SADeprecationWarning, |
---|
1476 | message % dict(func=fn.__name__), header) |
---|
1477 | return decorate |
---|
1478 | |
---|
1479 | def pending_deprecation(version, message=None, |
---|
1480 | add_deprecation_to_docstring=True): |
---|
1481 | """Decorates a function and issues a pending deprecation warning on use. |
---|
1482 | |
---|
1483 | version |
---|
1484 | An approximate future version at which point the pending deprecation |
---|
1485 | will become deprecated. Not used in messaging. |
---|
1486 | |
---|
1487 | message |
---|
1488 | If provided, issue message in the warning. A sensible default |
---|
1489 | is used if not provided. |
---|
1490 | |
---|
1491 | add_deprecation_to_docstring |
---|
1492 | Default True. If False, the wrapped function's __doc__ is left |
---|
1493 | as-is. If True, the 'message' is prepended to the docs if |
---|
1494 | provided, or sensible default if message is omitted. |
---|
1495 | """ |
---|
1496 | |
---|
1497 | if add_deprecation_to_docstring: |
---|
1498 | header = message is not None and message or 'Deprecated.' |
---|
1499 | else: |
---|
1500 | header = None |
---|
1501 | |
---|
1502 | if message is None: |
---|
1503 | message = "Call to deprecated function %(func)s" |
---|
1504 | |
---|
1505 | def decorate(fn): |
---|
1506 | return _decorate_with_warning( |
---|
1507 | fn, exc.SAPendingDeprecationWarning, |
---|
1508 | message % dict(func=fn.__name__), header) |
---|
1509 | return decorate |
---|
1510 | |
---|
1511 | def _decorate_with_warning(func, wtype, message, docstring_header=None): |
---|
1512 | """Wrap a function with a warnings.warn and augmented docstring.""" |
---|
1513 | |
---|
1514 | @decorator |
---|
1515 | def warned(fn, *args, **kwargs): |
---|
1516 | warnings.warn(wtype(message), stacklevel=3) |
---|
1517 | return fn(*args, **kwargs) |
---|
1518 | |
---|
1519 | doc = func.__doc__ is not None and func.__doc__ or '' |
---|
1520 | if docstring_header is not None: |
---|
1521 | docstring_header %= dict(func=func.__name__) |
---|
1522 | docs = doc and doc.expandtabs().split('\n') or [] |
---|
1523 | indent = '' |
---|
1524 | for line in docs[1:]: |
---|
1525 | text = line.lstrip() |
---|
1526 | if text: |
---|
1527 | indent = line[0:len(line) - len(text)] |
---|
1528 | break |
---|
1529 | point = min(len(docs), 1) |
---|
1530 | docs.insert(point, '\n' + indent + docstring_header.rstrip()) |
---|
1531 | doc = '\n'.join(docs) |
---|
1532 | |
---|
1533 | decorated = warned(func) |
---|
1534 | decorated.__doc__ = doc |
---|
1535 | return decorated |
---|