fake_shell.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import ast
  2. import cStringIO
  3. import datetime
  4. import decimal
  5. import json
  6. import logging
  7. import sys
  8. import traceback
  9. logging.basicConfig()
  10. logger = logging.getLogger('fake_shell')
  11. global_dict = {}
  12. execution_count = 0
  13. def execute_reply(status, content):
  14. global execution_count
  15. execution_count += 1
  16. return {
  17. 'msg_type': 'execute_reply',
  18. 'content': dict(
  19. content,
  20. status=status,
  21. execution_count=execution_count - 1
  22. )
  23. }
  24. def execute_reply_ok(data):
  25. return execute_reply('ok', {
  26. 'data': data,
  27. })
  28. def execute_reply_error(exc_type, exc_value, tb):
  29. logger.error('execute_reply', exc_info=True)
  30. return execute_reply('error', {
  31. 'ename': unicode(exc_type.__name__),
  32. 'evalue': unicode(exc_value),
  33. 'traceback': traceback.format_exception(exc_type, exc_value, tb, -1),
  34. })
  35. def execute(code):
  36. try:
  37. code = ast.parse(code)
  38. to_run_exec, to_run_single = code.body[:-1], code.body[-1:]
  39. for node in to_run_exec:
  40. mod = ast.Module([node])
  41. code = compile(mod, '<stdin>', 'exec')
  42. exec code in global_dict
  43. for node in to_run_single:
  44. mod = ast.Interactive([node])
  45. code = compile(mod, '<stdin>', 'single')
  46. exec code in global_dict
  47. except:
  48. return execute_reply_error(*sys.exc_info())
  49. stdout = fake_stdout.getvalue()
  50. fake_stdout.truncate(0)
  51. stderr = fake_stderr.getvalue()
  52. fake_stderr.truncate(0)
  53. output = ''
  54. if stdout:
  55. output += stdout
  56. if stderr:
  57. output += stderr
  58. return execute_reply_ok({
  59. 'text/plain': output.rstrip(),
  60. })
  61. def execute_request(content):
  62. try:
  63. code = content['code']
  64. except KeyError:
  65. exc_type, exc_value, tb = sys.exc_info()
  66. return execute_reply_error(exc_type, exc_value, [])
  67. lines = code.split('\n')
  68. if lines and lines[-1].startswith('%'):
  69. code, magic = lines[:-1], lines[-1]
  70. # Make sure to execute the other lines first.
  71. if code:
  72. result = execute('\n'.join(code))
  73. if result['content']['status'] != 'ok':
  74. return result
  75. parts = magic[1:].split(' ', 1)
  76. if len(parts) == 1:
  77. magic, rest = parts[0], ()
  78. else:
  79. magic, rest = parts[0], (parts[1],)
  80. try:
  81. handler = magic_router[magic]
  82. except KeyError:
  83. exc_type, exc_value, tb = sys.exc_info()
  84. return execute_reply_error(exc_type, exc_value, [])
  85. else:
  86. return handler(*rest)
  87. else:
  88. return execute(code)
  89. def magic_table_convert(value):
  90. try:
  91. converter = magic_table_types[type(value)]
  92. except KeyError:
  93. converter = magic_table_types[str]
  94. return converter(value)
  95. def magic_table_convert_seq(items):
  96. last_item_type = None
  97. converted_items = []
  98. for item in items:
  99. item_type, item = magic_table_convert(item)
  100. if last_item_type is None:
  101. last_item_type = item_type
  102. elif last_item_type != item_type:
  103. raise ValueError('value has inconsistent types')
  104. converted_items.append(item)
  105. return 'ARRAY_TYPE', converted_items
  106. def magic_table_convert_map(m):
  107. last_key_type = None
  108. last_value_type = None
  109. converted_items = {}
  110. for key, value in m:
  111. key_type, key = magic_table_convert(key)
  112. value_type, value = magic_table_convert(value)
  113. if last_key_type is None:
  114. last_key_type = key_type
  115. elif last_value_type != value_type:
  116. raise ValueError('value has inconsistent types')
  117. if last_value_type is None:
  118. last_value_type = value_type
  119. elif last_value_type != value_type:
  120. raise ValueError('value has inconsistent types')
  121. converted_items[key] = value
  122. return 'MAP_TYPE', items
  123. magic_table_types = {
  124. type(None): lambda x: ('NULL_TYPE', x),
  125. bool: lambda x: ('BOOLEAN_TYPE', x),
  126. int: lambda x: ('INT_TYPE', x),
  127. long: lambda x: ('BIGINT_TYPE', x),
  128. float: lambda x: ('DOUBLE_TYPE', x),
  129. str: lambda x: ('STRING_TYPE', str(x)),
  130. unicode: lambda x: ('STRING_TYPE', x.encode('utf-8')),
  131. datetime.date: lambda x: ('DATE_TYPE', str(x)),
  132. datetime.datetime: lambda x: ('TIMESTAMP_TYPE', str(x)),
  133. decimal.Decimal: lambda x: ('DECIMAL_TYPE', str(x)),
  134. tuple: magic_table_convert_seq,
  135. list: magic_table_convert_seq,
  136. dict: magic_table_convert_map,
  137. }
  138. def magic_table(name):
  139. try:
  140. value = global_dict[name]
  141. except KeyError:
  142. exc_type, exc_value, tb = sys.exc_info()
  143. return execute_reply_error(exc_type, exc_value, [])
  144. if not isinstance(value, (list, tuple)):
  145. value = [value]
  146. headers = {}
  147. data = []
  148. for row in value:
  149. cols = []
  150. data.append(cols)
  151. if not isinstance(row, (list, tuple, dict)):
  152. row = [row]
  153. if isinstance(row, (list, tuple)):
  154. iterator = enumerate(row)
  155. else:
  156. iterator = row.iteritems()
  157. for name, col in iterator:
  158. col_type, col = magic_table_convert(col)
  159. try:
  160. header = headers[name]
  161. except KeyError:
  162. header = {
  163. 'name': str(name),
  164. 'type': col_type,
  165. }
  166. headers[name] = header
  167. else:
  168. # Reject columns that have a different type.
  169. if header['type'] != col_type:
  170. exc_type = Exception
  171. exc_value = 'table rows have different types'
  172. return execute_reply_error(exc_type, exc_value, [])
  173. cols.append(col)
  174. headers = [v for k, v in sorted(headers.iteritems())]
  175. return execute_reply_ok({
  176. 'application/vnd.livy.table.v1+json': {
  177. 'headers': headers,
  178. 'data': data,
  179. }
  180. })
  181. def shutdown_request(content):
  182. sys.exit()
  183. magic_router = {
  184. 'table': magic_table,
  185. }
  186. msg_type_router = {
  187. 'execute_request': execute_request,
  188. 'shutdown_request': shutdown_request,
  189. }
  190. sys_stdin = sys.stdin
  191. sys_stdout = sys.stdout
  192. sys_stderr = sys.stderr
  193. fake_stdin = cStringIO.StringIO()
  194. fake_stdout = cStringIO.StringIO()
  195. fake_stderr = cStringIO.StringIO()
  196. sys.stdin = fake_stdin
  197. sys.stdout = fake_stdout
  198. sys.stderr = fake_stderr
  199. try:
  200. # Load spark into the context
  201. exec 'from pyspark.shell import sc' in global_dict
  202. print >> sys_stderr, fake_stdout.getvalue()
  203. print >> sys_stderr, fake_stderr.getvalue()
  204. fake_stdout.truncate(0)
  205. fake_stderr.truncate(0)
  206. print >> sys_stdout, 'READY'
  207. sys_stdout.flush()
  208. while True:
  209. line = sys_stdin.readline()
  210. if line == '':
  211. break
  212. elif line == '\n':
  213. continue
  214. try:
  215. msg = json.loads(line)
  216. except ValueError:
  217. logger.error('failed to parse message', exc_info=True)
  218. continue
  219. try:
  220. msg_type = msg['msg_type']
  221. except KeyError:
  222. logger.error('missing message type', exc_info=True)
  223. continue
  224. try:
  225. content = msg['content']
  226. except KeyError:
  227. logger.error('missing content', exc_info=True)
  228. continue
  229. try:
  230. handler = msg_type_router[msg_type]
  231. except KeyError:
  232. logger.error('unknown message type: %s', msg_type)
  233. continue
  234. response = handler(content)
  235. try:
  236. response = json.dumps(response)
  237. except ValueError, e:
  238. response = json.dumps({
  239. 'msg_type': 'inspect_reply',
  240. 'execution_count': execution_count - 1,
  241. 'content': {
  242. 'status': 'error',
  243. 'ename': 'ValueError',
  244. 'evalue': 'cannot json-ify %s' % response,
  245. 'traceback': [],
  246. }
  247. })
  248. print >> sys_stdout, response
  249. sys_stdout.flush()
  250. finally:
  251. global_dict['sc'].stop()
  252. sys.stdin = sys_stdin
  253. sys.stdout = sys_stdout
  254. sys.stderr = sys_stderr