PageRenderTime 51ms CodeModel.GetById 7ms RepoModel.GetById 1ms app.codeStats 0ms

/spyderlib/widgets/dicteditorutils.py

https://code.google.com/p/spyderlib/
Python | 288 lines | 231 code | 26 blank | 31 comment | 68 complexity | 6abb47c81a0a7380d12a04cb09ce4337 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright Š 2011 Pierre Raybaut
  4. # Licensed under the terms of the MIT License
  5. # (see spyderlib/__init__.py for details)
  6. """
  7. Utilities for the Dictionary Editor Widget and Dialog based on Qt
  8. """
  9. from __future__ import print_function
  10. import re
  11. # Local imports
  12. from spyderlib.py3compat import (NUMERIC_TYPES, TEXT_TYPES, to_text_string,
  13. is_text_string, is_binary_string, reprlib)
  14. from spyderlib.utils import programs
  15. from spyderlib import dependencies
  16. from spyderlib.baseconfig import _
  17. class FakeObject(object):
  18. """Fake class used in replacement of missing modules"""
  19. pass
  20. #----Numpy arrays support
  21. try:
  22. from numpy import ndarray
  23. from numpy import array, matrix #@UnusedImport (object eval)
  24. from numpy.ma import MaskedArray
  25. except ImportError:
  26. ndarray = array = matrix = MaskedArray = FakeObject # analysis:ignore
  27. def get_numpy_dtype(obj):
  28. """Return NumPy data type associated to obj
  29. Return None if NumPy is not available
  30. or if obj is not a NumPy array or scalar"""
  31. if ndarray is not FakeObject:
  32. # NumPy is available
  33. import numpy as np
  34. if isinstance(obj, np.generic) or isinstance(obj, np.ndarray):
  35. # Numpy scalars all inherit from np.generic.
  36. # Numpy arrays all inherit from np.ndarray.
  37. # If we check that we are certain we have one of these
  38. # types then we are less likely to generate an exception below.
  39. try:
  40. return obj.dtype.type
  41. except (AttributeError, RuntimeError):
  42. # AttributeError: some NumPy objects have no dtype attribute
  43. # RuntimeError: happens with NetCDF objects (Issue 998)
  44. return
  45. #----Pandas support
  46. PANDAS_REQVER = '>=0.13.1'
  47. dependencies.add('pandas', _("View and edit DataFrames and Series in the "
  48. "Variable Explorer"),
  49. required_version=PANDAS_REQVER)
  50. if programs.is_module_installed('pandas', PANDAS_REQVER):
  51. from pandas import DataFrame, TimeSeries
  52. else:
  53. DataFrame = TimeSeries = FakeObject # analysis:ignore
  54. #----PIL Images support
  55. try:
  56. from spyderlib import pil_patch
  57. Image = pil_patch.Image.Image
  58. except ImportError:
  59. Image = FakeObject # analysis:ignore
  60. #----Misc.
  61. def address(obj):
  62. """Return object address as a string: '<classname @ address>'"""
  63. return "<%s @ %s>" % (obj.__class__.__name__,
  64. hex(id(obj)).upper().replace('X', 'x'))
  65. #----Set limits for the amount of elements in the repr of collections
  66. # (lists, dicts, tuples and sets)
  67. CollectionsRepr = reprlib.Repr()
  68. CollectionsRepr.maxlist = 1000
  69. CollectionsRepr.maxdict = 1000
  70. CollectionsRepr.maxtuple = 1000
  71. CollectionsRepr.maxset = 1000
  72. #----date and datetime objects support
  73. import datetime
  74. try:
  75. from dateutil.parser import parse as dateparse
  76. except ImportError:
  77. def dateparse(datestr): # analysis:ignore
  78. """Just for 'year, month, day' strings"""
  79. return datetime.datetime( *list(map(int, datestr.split(','))) )
  80. def datestr_to_datetime(value):
  81. rp = value.rfind('(')+1
  82. v = dateparse(value[rp:-1])
  83. print(value, "-->", v)
  84. return v
  85. #----Background colors for supported types
  86. ARRAY_COLOR = "#00ff00"
  87. SCALAR_COLOR = "#0000ff"
  88. COLORS = {
  89. bool: "#ff00ff",
  90. NUMERIC_TYPES: SCALAR_COLOR,
  91. list: "#ffff00",
  92. dict: "#00ffff",
  93. tuple: "#c0c0c0",
  94. TEXT_TYPES: "#800000",
  95. (ndarray,
  96. MaskedArray,
  97. matrix,
  98. DataFrame,
  99. TimeSeries): ARRAY_COLOR,
  100. Image: "#008000",
  101. datetime.date: "#808000",
  102. }
  103. CUSTOM_TYPE_COLOR = "#7755aa"
  104. UNSUPPORTED_COLOR = "#ffffff"
  105. def get_color_name(value):
  106. """Return color name depending on value type"""
  107. if not is_known_type(value):
  108. return CUSTOM_TYPE_COLOR
  109. for typ, name in list(COLORS.items()):
  110. if isinstance(value, typ):
  111. return name
  112. else:
  113. np_dtype = get_numpy_dtype(value)
  114. if np_dtype is None or not hasattr(value, 'size'):
  115. return UNSUPPORTED_COLOR
  116. elif value.size == 1:
  117. return SCALAR_COLOR
  118. else:
  119. return ARRAY_COLOR
  120. def is_editable_type(value):
  121. """Return True if data type is editable with a standard GUI-based editor,
  122. like DictEditor, ArrayEditor, QDateEdit or a simple QLineEdit"""
  123. return get_color_name(value) not in (UNSUPPORTED_COLOR, CUSTOM_TYPE_COLOR)
  124. #----Sorting
  125. def sort_against(lista, listb, reverse=False):
  126. """Arrange lista items in the same order as sorted(listb)"""
  127. try:
  128. return [item for _, item in sorted(zip(listb, lista), reverse=reverse)]
  129. except:
  130. return lista
  131. def unsorted_unique(lista):
  132. """Removes duplicates from lista neglecting its initial ordering"""
  133. return list(set(lista))
  134. #----Display <--> Value
  135. def value_to_display(value, truncate=False, trunc_len=80, minmax=False):
  136. """Convert value for display purpose"""
  137. if minmax and isinstance(value, (ndarray, MaskedArray)):
  138. if value.size == 0:
  139. return repr(value)
  140. try:
  141. return 'Min: %r\nMax: %r' % (value.min(), value.max())
  142. except TypeError:
  143. pass
  144. except ValueError:
  145. # Happens when one of the array cell contains a sequence
  146. pass
  147. if isinstance(value, Image):
  148. return '%s Mode: %s' % (address(value), value.mode)
  149. if isinstance(value, DataFrame):
  150. cols = value.columns
  151. cols = [to_text_string(c) for c in cols]
  152. return 'Column names: ' + ', '.join(list(cols))
  153. if is_binary_string(value):
  154. try:
  155. value = to_text_string(value, 'utf8')
  156. except:
  157. pass
  158. if not is_text_string(value):
  159. if isinstance(value, (list, tuple, dict, set)):
  160. value = CollectionsRepr.repr(value)
  161. else:
  162. value = repr(value)
  163. if truncate and len(value) > trunc_len:
  164. value = value[:trunc_len].rstrip() + ' ...'
  165. return value
  166. def try_to_eval(value):
  167. """Try to eval value"""
  168. try:
  169. return eval(value)
  170. except (NameError, SyntaxError, ImportError):
  171. return value
  172. def get_size(item):
  173. """Return size of an item of arbitrary type"""
  174. if isinstance(item, (list, tuple, dict)):
  175. return len(item)
  176. elif isinstance(item, (ndarray, MaskedArray)):
  177. return item.shape
  178. elif isinstance(item, Image):
  179. return item.size
  180. if isinstance(item, (DataFrame, TimeSeries)):
  181. return item.shape
  182. else:
  183. return 1
  184. def get_type_string(item):
  185. """Return type string of an object"""
  186. if isinstance(item, DataFrame):
  187. return "DataFrame"
  188. if isinstance(item, TimeSeries):
  189. return "TimeSeries"
  190. found = re.findall(r"<(?:type|class) '(\S*)'>", str(type(item)))
  191. if found:
  192. return found[0]
  193. def is_known_type(item):
  194. """Return True if object has a known type"""
  195. # Unfortunately, the masked array case is specific
  196. return isinstance(item, MaskedArray) or get_type_string(item) is not None
  197. def get_human_readable_type(item):
  198. """Return human-readable type string of an item"""
  199. if isinstance(item, (ndarray, MaskedArray)):
  200. return item.dtype.name
  201. elif isinstance(item, Image):
  202. return "Image"
  203. else:
  204. text = get_type_string(item)
  205. if text is None:
  206. text = to_text_string('unknown')
  207. else:
  208. return text[text.find('.')+1:]
  209. #----Globals filter: filter namespace dictionaries (to be edited in DictEditor)
  210. def is_supported(value, check_all=False, filters=None, iterate=True):
  211. """Return True if the value is supported, False otherwise"""
  212. assert filters is not None
  213. if not is_editable_type(value):
  214. return False
  215. elif not isinstance(value, filters):
  216. return False
  217. elif iterate:
  218. if isinstance(value, (list, tuple, set)):
  219. for val in value:
  220. if not is_supported(val, filters=filters, iterate=check_all):
  221. return False
  222. if not check_all:
  223. break
  224. elif isinstance(value, dict):
  225. for key, val in list(value.items()):
  226. if not is_supported(key, filters=filters, iterate=check_all) \
  227. or not is_supported(val, filters=filters,
  228. iterate=check_all):
  229. return False
  230. if not check_all:
  231. break
  232. return True
  233. def globalsfilter(input_dict, check_all=False, filters=None,
  234. exclude_private=None, exclude_capitalized=None,
  235. exclude_uppercase=None, exclude_unsupported=None,
  236. excluded_names=None):
  237. """Keep only objects that can be pickled"""
  238. output_dict = {}
  239. for key, value in list(input_dict.items()):
  240. excluded = (exclude_private and key.startswith('_')) or \
  241. (exclude_capitalized and key[0].isupper()) or \
  242. (exclude_uppercase and key.isupper()
  243. and len(key) > 1 and not key[1:].isdigit()) or \
  244. (key in excluded_names) or \
  245. (exclude_unsupported and \
  246. not is_supported(value, check_all=check_all,
  247. filters=filters))
  248. if not excluded:
  249. output_dict[key] = value
  250. return output_dict