comparison env/lib/python3.9/site-packages/galaxy/util/object_wrapper.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """
2 Classes for wrapping Objects and Sanitizing string output.
3 """
4
5 import copyreg
6 import inspect
7 import logging
8 import string
9 from collections import UserDict
10 from collections.abc import Callable
11 from numbers import Number
12 from types import (
13 BuiltinFunctionType,
14 BuiltinMethodType,
15 CodeType,
16 FrameType,
17 FunctionType,
18 GeneratorType,
19 GetSetDescriptorType,
20 MemberDescriptorType,
21 MethodType,
22 ModuleType,
23 TracebackType,
24 )
25
26 NoneType = type(None)
27 NotImplementedType = type(NotImplemented)
28 EllipsisType = type(Ellipsis)
29 XRangeType = range
30 SliceType = slice
31
32 # Python 2 version was:
33 # from types import BufferType, DictProxyType
34 # Py3 doesn't have these concepts, just treat them like SliceType that
35 # so they are __WRAP_NO_SUBCLASS__.
36 BufferType = SliceType
37 DictProxyType = SliceType
38
39 from galaxy.util import sanitize_lists_to_string as _sanitize_lists_to_string
40
41 log = logging.getLogger(__name__)
42
43 # Define different behaviors for different types, see also: https://docs.python.org/2/library/types.html
44
45 # Known Callable types
46 __CALLABLE_TYPES__ = (FunctionType, MethodType, GeneratorType, CodeType, BuiltinFunctionType, BuiltinMethodType, )
47
48 # Always wrap these types without attempting to subclass
49 __WRAP_NO_SUBCLASS__ = (ModuleType, XRangeType, SliceType, BufferType, TracebackType, FrameType, DictProxyType,
50 GetSetDescriptorType, MemberDescriptorType) + __CALLABLE_TYPES__
51
52 # Don't wrap or sanitize.
53 __DONT_SANITIZE_TYPES__ = (Number, bool, NoneType, NotImplementedType, EllipsisType, bytearray, )
54
55 # Wrap contents, but not the container
56 __WRAP_SEQUENCES__ = (tuple, list, )
57 __WRAP_SETS__ = (set, frozenset, )
58 __WRAP_MAPPINGS__ = (dict, UserDict, )
59
60
61 # Define the set of characters that are not sanitized, and define a set of mappings for those that are.
62 # characters that are valid
63 VALID_CHARACTERS = set(string.ascii_letters + string.digits + " -=_.()/+*^,:?!@")
64
65 # characters that are allowed but need to be escaped
66 CHARACTER_MAP = {'>': '__gt__',
67 '<': '__lt__',
68 "'": '__sq__',
69 '"': '__dq__',
70 '[': '__ob__',
71 ']': '__cb__',
72 '{': '__oc__',
73 '}': '__cc__',
74 '\n': '__cn__',
75 '\r': '__cr__',
76 '\t': '__tc__',
77 '#': '__pd__'}
78
79 INVALID_CHARACTER = "X"
80
81
82 def coerce(x, y):
83 # __coerce__ doesn't do anything under Python anyway.
84 return x
85
86
87 def cmp(x, y):
88 # Builtin in Python 2, but not Python 3.
89 return (x > y) - (x < y)
90
91
92 def sanitize_lists_to_string(values, valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP, invalid_character=INVALID_CHARACTER):
93 return _sanitize_lists_to_string(values, valid_characters=valid_characters, character_map=character_map, invalid_character=invalid_character)
94
95
96 def wrap_with_safe_string(value, no_wrap_classes=None):
97 """
98 Recursively wrap values that should be wrapped.
99 """
100
101 def __do_wrap(value):
102 if isinstance(value, SafeStringWrapper):
103 # Only ever wrap one-layer
104 return value
105 if isinstance(value, Callable):
106 safe_class = CallableSafeStringWrapper
107 else:
108 safe_class = SafeStringWrapper
109 if isinstance(value, no_wrap_classes):
110 return value
111 if isinstance(value, __WRAP_NO_SUBCLASS__):
112 return safe_class(value, safe_string_wrapper_function=__do_wrap)
113 for this_type in __WRAP_SEQUENCES__ + __WRAP_SETS__:
114 if isinstance(value, this_type):
115 return this_type(list(map(__do_wrap, value)))
116 for this_type in __WRAP_MAPPINGS__:
117 if isinstance(value, this_type):
118 # Wrap both key and value
119 return this_type((__do_wrap(x[0]), __do_wrap(x[1])) for x in value.items())
120 # Create a dynamic class that joins SafeStringWrapper with the object being wrapped.
121 # This allows e.g. isinstance to continue to work.
122 try:
123 wrapped_class_name = value.__name__
124 wrapped_class = value
125 except Exception:
126 wrapped_class_name = value.__class__.__name__
127 wrapped_class = value.__class__
128 value_mod = inspect.getmodule(value)
129 if value_mod:
130 wrapped_class_name = f"{value_mod.__name__}.{wrapped_class_name}"
131 wrapped_class_name = "SafeStringWrapper({}:{})".format(wrapped_class_name, ",".join(sorted(map(str, no_wrap_classes))))
132 do_wrap_func_name = f"__do_wrap_{wrapped_class_name}"
133 do_wrap_func = __do_wrap
134 global_dict = globals()
135 if wrapped_class_name in global_dict:
136 # Check to see if we have created a wrapper for this class yet, if so, reuse
137 wrapped_class = global_dict.get(wrapped_class_name)
138 do_wrap_func = global_dict.get(do_wrap_func_name, __do_wrap)
139 else:
140 try:
141 wrapped_class = type(wrapped_class_name, (safe_class, wrapped_class, ), {})
142 except TypeError as e:
143 # Fail-safe for when a class cannot be dynamically subclassed.
144 log.warning(f"Unable to create dynamic subclass {wrapped_class_name} for {type(value)}, {value}: {e}")
145 wrapped_class = type(wrapped_class_name, (safe_class, ), {})
146 if wrapped_class not in (SafeStringWrapper, CallableSafeStringWrapper):
147 # Save this wrapper for reuse and pickling/copying
148 global_dict[wrapped_class_name] = wrapped_class
149 do_wrap_func.__name__ = do_wrap_func_name
150 global_dict[do_wrap_func_name] = do_wrap_func
151
152 def pickle_safe_object(safe_object):
153 return (wrapped_class, (safe_object.unsanitized, do_wrap_func, ))
154 # Set pickle and copy properties
155 copyreg.pickle(wrapped_class, pickle_safe_object, do_wrap_func)
156 return wrapped_class(value, safe_string_wrapper_function=do_wrap_func)
157
158 # Determine classes not to wrap
159 if no_wrap_classes:
160 if not isinstance(no_wrap_classes, (tuple, list)):
161 no_wrap_classes = [no_wrap_classes]
162 no_wrap_classes = list(no_wrap_classes) + list(__DONT_SANITIZE_TYPES__) + [SafeStringWrapper]
163 else:
164 no_wrap_classes = list(__DONT_SANITIZE_TYPES__) + [SafeStringWrapper]
165 no_wrap_classes = tuple(set(sorted(no_wrap_classes, key=str)))
166 return __do_wrap(value)
167
168
169 # N.B. refer to e.g. https://docs.python.org/reference/datamodel.html for information on Python's Data Model.
170
171
172 class SafeStringWrapper:
173 """
174 Class that wraps and sanitizes any provided value's attributes
175 that will attempt to be cast into a string.
176
177 Attempts to mimic behavior of original class, including operands.
178
179 To ensure proper handling of e.g. subclass checks, the *wrap_with_safe_string()*
180 method should be used.
181
182 This wrapping occurs in a recursive/parasitic fashion, as all called attributes of
183 the originally wrapped object will also be wrapped and sanitized, unless the attribute
184 is of a type found in __DONT_SANITIZE_TYPES__ + __DONT_WRAP_TYPES__, where e.g. ~(strings
185 will still be sanitized, but not wrapped), and e.g. integers will have neither.
186 """
187 __UNSANITIZED_ATTRIBUTE_NAME__ = 'unsanitized'
188 __NO_WRAP_NAMES__ = ['__safe_string_wrapper_function__', '__class__', __UNSANITIZED_ATTRIBUTE_NAME__]
189
190 def __new__(cls, *arg, **kwd):
191 # We need to define a __new__ since, we are subclassing from e.g. immutable str, which internally sets data
192 # that will be used when other + this (this + other is handled by __add__)
193 try:
194 sanitized_value = sanitize_lists_to_string(arg[0], valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP)
195 return super().__new__(cls, sanitized_value)
196 except TypeError:
197 # Class to be wrapped takes no parameters.
198 # This is pefectly normal for mutable types.
199 return super().__new__(cls)
200
201 def __init__(self, value, safe_string_wrapper_function=wrap_with_safe_string):
202 self.unsanitized = value
203 self.__safe_string_wrapper_function__ = safe_string_wrapper_function
204
205 def __str__(self):
206 return sanitize_lists_to_string(self.unsanitized, valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP)
207
208 def __repr__(self):
209 return "{} object at {:x} on: {}".format(sanitize_lists_to_string(self.__class__.__name__, valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP), id(self), sanitize_lists_to_string(repr(self.unsanitized), valid_characters=VALID_CHARACTERS, character_map=CHARACTER_MAP))
210
211 def __lt__(self, other):
212 while isinstance(other, SafeStringWrapper):
213 other = other.unsanitized
214 return self.unsanitized < other
215
216 def __le__(self, other):
217 while isinstance(other, SafeStringWrapper):
218 other = other.unsanitized
219 return self.unsanitized <= other
220
221 def __eq__(self, other):
222 while isinstance(other, SafeStringWrapper):
223 other = other.unsanitized
224 return self.unsanitized == other
225
226 def __ne__(self, other):
227 while isinstance(other, SafeStringWrapper):
228 other = other.unsanitized
229 return self.unsanitized != other
230
231 def __gt__(self, other):
232 while isinstance(other, SafeStringWrapper):
233 other = other.unsanitized
234 return self.unsanitized > other
235
236 def __ge__(self, other):
237 while isinstance(other, SafeStringWrapper):
238 other = other.unsanitized
239 return self.unsanitized >= other
240
241 def __cmp__(self, other):
242 while isinstance(other, SafeStringWrapper):
243 other = other.unsanitized
244 return cmp(self.unsanitized, other)
245
246 # Do not implement __rcmp__, python 2.2 < 2.6
247
248 def __hash__(self):
249 return hash(self.unsanitized)
250
251 def __bool__(self):
252 return bool(self.unsanitized)
253 __nonzero__ = __bool__
254
255 # Do not implement __unicode__, we will rely on __str__
256
257 def __getattr__(self, name):
258 if name in SafeStringWrapper.__NO_WRAP_NAMES__:
259 # FIXME: is this ever reached?
260 return object.__getattribute__(self, name)
261 return self.__safe_string_wrapper_function__(getattr(self.unsanitized, name))
262
263 def __setattr__(self, name, value):
264 if name in SafeStringWrapper.__NO_WRAP_NAMES__:
265 return object.__setattr__(self, name, value)
266 return setattr(self.unsanitized, name, value)
267
268 def __delattr__(self, name):
269 if name in SafeStringWrapper.__NO_WRAP_NAMES__:
270 return object.__delattr__(self, name)
271 return delattr(self.unsanitized, name)
272
273 def __getattribute__(self, name):
274 if name in SafeStringWrapper.__NO_WRAP_NAMES__:
275 return object.__getattribute__(self, name)
276 return self.__safe_string_wrapper_function__(getattr(object.__getattribute__(self, 'unsanitized'), name))
277
278 # Skip Descriptors
279
280 # Skip __slots__
281
282 # Don't need to define a metaclass, we'll use the helper function to handle with subclassing for e.g. isinstance()
283
284 # Revisit:
285 # __instancecheck__
286 # __subclasscheck__
287 # We are using a helper class to create dynamic subclasses to handle class checks
288
289 # We address __call__ as needed based upon unsanitized, through the use of a CallableSafeStringWrapper class
290
291 def __len__(self):
292 original_value = self.unsanitized
293 while isinstance(original_value, SafeStringWrapper):
294 original_value = self.unsanitized
295 return len(self.unsanitized)
296
297 def __getitem__(self, key):
298 return self.__safe_string_wrapper_function__(self.unsanitized[key])
299
300 def __setitem__(self, key, value):
301 while isinstance(value, SafeStringWrapper):
302 value = value.unsanitized
303 self.unsanitized[key] = value
304
305 def __delitem__(self, key):
306 del self.unsanitized[key]
307
308 def __iter__(self):
309 return iter(map(self.__safe_string_wrapper_function__, iter(self.unsanitized)))
310
311 # Do not implement __reversed__
312
313 def __contains__(self, item):
314 # FIXME: Do we need to consider if item is/isn't or does/doesn't contain SafeStringWrapper?
315 # When considering e.g. nested lists/dicts/etc, this gets complicated
316 while isinstance(item, SafeStringWrapper):
317 item = item.unsanitized
318 return item in self.unsanitized
319
320 # Not sure that we need these slice methods, but will provide anyway
321 def __getslice__(self, i, j):
322 return self.__safe_string_wrapper_function__(self.unsanitized[i:j])
323
324 def __setslice__(self, i, j, value):
325 self.unsanitized[i:j] = value
326
327 def __delslice__(self, i, j):
328 del self.unsanitized[i:j]
329
330 def __add__(self, other):
331 while isinstance(other, SafeStringWrapper):
332 other = other.unsanitized
333 return self.__safe_string_wrapper_function__(self.unsanitized + other)
334
335 def __sub__(self, other):
336 while isinstance(other, SafeStringWrapper):
337 other = other.unsanitized
338 return self.__safe_string_wrapper_function__(self.unsanitized - other)
339
340 def __mul__(self, other):
341 while isinstance(other, SafeStringWrapper):
342 other = other.unsanitized
343 return self.__safe_string_wrapper_function__(self.unsanitized * other)
344
345 def __floordiv__(self, other):
346 while isinstance(other, SafeStringWrapper):
347 other = other.unsanitized
348 return self.__safe_string_wrapper_function__(self.unsanitized // other)
349
350 def __mod__(self, other):
351 while isinstance(other, SafeStringWrapper):
352 other = other.unsanitized
353 return self.__safe_string_wrapper_function__(self.unsanitized % other)
354
355 def __divmod__(self, other):
356 while isinstance(other, SafeStringWrapper):
357 other = other.unsanitized
358 return self.__safe_string_wrapper_function__(divmod(self.unsanitized, other))
359
360 def __pow__(self, *other):
361 while isinstance(other, SafeStringWrapper):
362 other = other.unsanitized
363 return self.__safe_string_wrapper_function__(pow(self.unsanitized, *other))
364
365 def __lshift__(self, other):
366 while isinstance(other, SafeStringWrapper):
367 other = other.unsanitized
368 return self.__safe_string_wrapper_function__(self.unsanitized << other)
369
370 def __rshift__(self, other):
371 while isinstance(other, SafeStringWrapper):
372 other = other.unsanitized
373 return self.__safe_string_wrapper_function__(self.unsanitized >> other)
374
375 def __and__(self, other):
376 while isinstance(other, SafeStringWrapper):
377 other = other.unsanitized
378 return self.__safe_string_wrapper_function__(self.unsanitized & other)
379
380 def __xor__(self, other):
381 while isinstance(other, SafeStringWrapper):
382 other = other.unsanitized
383 return self.__safe_string_wrapper_function__(self.unsanitized ^ other)
384
385 def __or__(self, other):
386 while isinstance(other, SafeStringWrapper):
387 other = other.unsanitized
388 return self.__safe_string_wrapper_function__(self.unsanitized | other)
389
390 def __div__(self, other):
391 while isinstance(other, SafeStringWrapper):
392 other = other.unsanitized
393 return self.__safe_string_wrapper_function__(self.unsanitized / other)
394
395 def __truediv__(self, other):
396 while isinstance(other, SafeStringWrapper):
397 other = other.unsanitized
398 return self.__safe_string_wrapper_function__(self.unsanitized / other)
399
400 # The only reflected operand that we will define is __rpow__, due to coercion rules complications as per docs
401 def __rpow__(self, other):
402 while isinstance(other, SafeStringWrapper):
403 other = other.unsanitized
404 return self.__safe_string_wrapper_function__(pow(other, self.unsanitized))
405
406 # Do not implement in-place operands
407
408 def __neg__(self):
409 return self.__safe_string_wrapper_function__(-self.unsanitized)
410
411 def __pos__(self):
412 return self.__safe_string_wrapper_function__(+self.unsanitized)
413
414 def __abs__(self):
415 return self.__safe_string_wrapper_function__(abs(self.unsanitized))
416
417 def __invert__(self):
418 return self.__safe_string_wrapper_function__(~self.unsanitized)
419
420 def __complex__(self):
421 return self.__safe_string_wrapper_function__(complex(self.unsanitized))
422
423 def __int__(self):
424 return int(self.unsanitized)
425
426 def __float__(self):
427 return float(self.unsanitized)
428
429 def __oct__(self):
430 return oct(self.unsanitized)
431
432 def __hex__(self):
433 return hex(self.unsanitized)
434
435 def __index__(self):
436 return self.unsanitized.index()
437
438 def __coerce__(self, other):
439 while isinstance(other, SafeStringWrapper):
440 other = other.unsanitized
441 return coerce(self.unsanitized, other)
442
443 def __enter__(self):
444 return self.unsanitized.__enter__()
445
446 def __exit__(self, *args):
447 return self.unsanitized.__exit__(*args)
448
449
450 class CallableSafeStringWrapper(SafeStringWrapper):
451
452 def __call__(self, *args, **kwds):
453 return self.__safe_string_wrapper_function__(self.unsanitized(*args, **kwds))
454
455
456 # Enable pickling/deepcopy
457 def pickle_SafeStringWrapper(safe_object):
458 args = (safe_object.unsanitized, )
459 cls = SafeStringWrapper
460 if isinstance(safe_object, CallableSafeStringWrapper):
461 cls = CallableSafeStringWrapper
462 return (cls, args)
463
464
465 copyreg.pickle(SafeStringWrapper, pickle_SafeStringWrapper, wrap_with_safe_string)
466 copyreg.pickle(CallableSafeStringWrapper, pickle_SafeStringWrapper, wrap_with_safe_string)