Skip to content

Commit 569f51a

Browse files
committed
Merge branch 'master' into release
2 parents 9912e56 + eed2259 commit 569f51a

22 files changed

+846
-46
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
## NEXT RELEASE
44

5+
## 0.19.0
6+
7+
### Features
8+
9+
* Added one internal magic to enable retry of session creation. Thanks @edwardps
10+
* New `%%pretty` magic for pretty printing a dataframe as an HTML table. Thanks @hegary
11+
* Update Endpoint widget to shield passwords when entering them in the ipywidget. Thanks @J0rg3M3nd3z @jodom961
12+
513
## 0.18.0
614

715
### Updates
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.18.0'
1+
__version__ = '0.19.0'

autovizwidget/autovizwidget/tests/test_autovizwidget.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from mock import MagicMock, call
22
from nose.tools import with_setup, assert_equals
33
import pandas as pd
4-
from pandas.util.testing import assert_frame_equal, assert_series_equal
4+
from pandas.testing import assert_frame_equal, assert_series_equal
55

66
from ..widget.autovizwidget import AutoVizWidget
77
from ..widget.encoding import Encoding

examples/Magics in IPython Kernel.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"cell_type": "markdown",
1212
"metadata": {},
1313
"source": [
14-
"## This notebook will demonstrate how we can use the spark magic to interspere our Python code with code that is running against a Spark cluster"
14+
"## This notebook will demonstrate how we can use the spark magic to intersperse our Python code with code that is running against a Spark cluster"
1515
]
1616
},
1717
{

examples/Pyspark Kernel.ipynb

+47-1
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,53 @@
681681
"Use '`%%cleanup -f`' magic to delete all of the sessions for this cluster, including this notebook's session.\n",
682682
"The force flag `-f` is mandatory."
683683
]
684-
}
684+
},
685+
{
686+
"cell_type": "markdown",
687+
"metadata": {},
688+
"source": [
689+
"### Pretty Print Spark Dataframes (%%pretty)\n",
690+
"\n",
691+
"Use '`%%pretty`' magic to display a Spark dataframe as a HTML formatted table"
692+
]
693+
},
694+
{
695+
"cell_type": "code",
696+
"execution_count": 3,
697+
"metadata": {},
698+
"outputs": [
699+
{
700+
"data": {
701+
"application/vnd.jupyter.widget-view+json": {
702+
"model_id": "",
703+
"version_major": 2,
704+
"version_minor": 0
705+
},
706+
"text/plain": [
707+
]
708+
},
709+
"metadata": {},
710+
"output_type": "display_data"
711+
},
712+
{
713+
"data": {
714+
"text/html": [
715+
"<table><tr><th>age</th><th>name</th></tr><tr><td>null</td><td>Michael</td></tr><tr><td>30</td><td>Andy</td></tr><tr><td>19</td><td>Justin</td></tr></table><br /><pre></pre>"
716+
],
717+
"text/plain": [
718+
"<IPython.core.display.HTML object>"
719+
]
720+
},
721+
"metadata": {},
722+
"output_type": "display_data"
723+
}
724+
],
725+
"source": [
726+
"%%pretty\n",
727+
"df = spark.read.json(\"/apps/spark-2.3.3/examples/src/main/resources/people.json\")\n",
728+
"df.show()"
729+
]
730+
}
685731
],
686732
"metadata": {
687733
"kernelspec": {

examples/Spark Kernel.ipynb

+53
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,59 @@
527527
"Use '`%%cleanup -f`' magic to delete all of the sessions for this cluster, including this notebook's session.\n",
528528
"The force flag `-f` is mandatory."
529529
]
530+
},
531+
{
532+
"cell_type": "markdown",
533+
"metadata": {},
534+
"source": [
535+
"### Pretty Print Spark Dataframes (%%pretty)\n",
536+
"\n",
537+
"Use '`%%pretty`' magic to display a Scala Spark dataframe as a HTML formatted table"
538+
]
539+
},
540+
{
541+
"cell_type": "code",
542+
"execution_count": 7,
543+
"metadata": {},
544+
"outputs": [
545+
{
546+
"data": {
547+
"application/vnd.jupyter.widget-view+json": {
548+
"model_id": "",
549+
"version_major": 2,
550+
"version_minor": 0
551+
},
552+
"text/plain": [
553+
"FloatProgress(value=0.0, bar_style='info', description='Progress:', layout=Layout(height='25px', width='50%'),…"
554+
]
555+
},
556+
"metadata": {},
557+
"output_type": "display_data"
558+
},
559+
{
560+
"data": {
561+
"text/html": [
562+
"<table><tr><th>col1</th><th>col2</th><th>col3</th></tr><tr><td>28</td><td>44</td><td>36</td></tr><tr><td>16</td><td>41</td><td>72</td></tr><tr><td>27</td><td>14</td><td>45</td></tr></table><br /><pre></pre>"
563+
],
564+
"text/plain": [
565+
"<IPython.core.display.HTML object>"
566+
]
567+
},
568+
"metadata": {},
569+
"output_type": "display_data"
570+
}
571+
],
572+
"source": [
573+
"%%pretty\n",
574+
"df.show()"
575+
]
576+
},
577+
{
578+
"cell_type": "code",
579+
"execution_count": null,
580+
"metadata": {},
581+
"outputs": [],
582+
"source": []
530583
}
531584
],
532585
"metadata": {
+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.18.0'
1+
__version__ = '0.19.0'

hdijupyterutils/hdijupyterutils/ipywidgetfactory.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Copyright (c) 2015 [email protected]
22
# Distributed under the terms of the Modified BSD License.
33

4-
from ipywidgets import VBox, Output, Button, HTML, HBox, Dropdown, Checkbox, ToggleButtons, Text, Textarea, Tab
4+
from ipywidgets import VBox, Output, Button, HTML, HBox, Dropdown, Checkbox, ToggleButtons, Text, Textarea, Tab, Password
55

66

77
class IpyWidgetFactory(object):
@@ -43,6 +43,10 @@ def get_toggle_buttons(**kwargs):
4343
def get_text(**kwargs):
4444
return Text(**kwargs)
4545

46+
@staticmethod
47+
def get_password(**kwargs):
48+
return Password(**kwargs)
49+
4650
@staticmethod
4751
def get_text_area(**kwargs):
4852
return Textarea(**kwargs)

sparkmagic/sparkmagic/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '0.18.0'
1+
__version__ = '0.19.0'
22

33
from sparkmagic.serverextension.handlers import load_jupyter_server_extension
44

sparkmagic/sparkmagic/auth/basic.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(self, parsed_attributes=None):
1818
is created from parsing %spark magic command.
1919
"""
2020
if parsed_attributes is not None:
21-
if parsed_attributes.user is '' or parsed_attributes.password is '':
21+
if parsed_attributes.user == '' or parsed_attributes.password == '':
2222
new_exc = BadUserDataException("Need to supply username and password arguments for "\
2323
"Basic Access Authentication. (e.g. -a username -p password).")
2424
raise new_exc
@@ -47,7 +47,7 @@ def get_widgets(self, widget_width):
4747
width=widget_width
4848
)
4949

50-
self.password_widget = ipywidget_factory.get_text(
50+
self.password_widget = ipywidget_factory.get_password(
5151
description='Password:',
5252
value=self.password,
5353
width=widget_width

sparkmagic/sparkmagic/kernels/kernelmagics.py

+52-16
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818
from sparkmagic.utils.utils import parse_argstring_or_throw, get_coerce_value, initialize_auth, Namespace
1919
from sparkmagic.utils.sparkevents import SparkEvents
2020
from sparkmagic.utils.constants import LANGS_SUPPORTED
21+
from sparkmagic.utils.dataframe_parser import cell_contains_dataframe, CellOutputHtmlParser
2122
from sparkmagic.livyclientlib.command import Command
2223
from sparkmagic.livyclientlib.endpoint import Endpoint
23-
from sparkmagic.magics.sparkmagicsbase import SparkMagicBase
24+
from sparkmagic.magics.sparkmagicsbase import SparkMagicBase, SparkOutputHandler
2425
from sparkmagic.livyclientlib.exceptions import handle_expected_exceptions, wrap_unexpected_exceptions, \
2526
BadUserDataException
2627

@@ -57,6 +58,7 @@ def __init__(self, shell, data=None, spark_events=None):
5758
self.language = u""
5859
self.endpoint = None
5960
self.fatal_error = False
61+
self.allow_retry_fatal = False
6062
self.fatal_error_message = u""
6163
if spark_events is None:
6264
spark_events = SparkEvents()
@@ -153,6 +155,11 @@ def help(self, line, cell="", local_ns=None):
153155
</ul>
154156
</td>
155157
</tr>
158+
<tr>
159+
<td>pretty</td>
160+
<td>%%pretty</td>
161+
<td>If the cell output is a dataframe, like <code>df.show()</code>, then it will pretty print the dataframe as an HTML table</td>
162+
</tr>
156163
</table>
157164
"""
158165
self.ipython_display.html(help_html)
@@ -258,14 +265,36 @@ def configure(self, line, cell="", local_ns=None):
258265
@wrap_unexpected_exceptions
259266
@handle_expected_exceptions
260267
def spark(self, line, cell="", local_ns=None):
261-
if self._do_not_call_start_session(u""):
262-
args = parse_argstring_or_throw(self.spark, line)
268+
if not self._do_not_call_start_session(u""):
269+
return
263270

264-
coerce = get_coerce_value(args.coerce)
271+
args = parse_argstring_or_throw(self.spark, line)
272+
273+
coerce = get_coerce_value(args.coerce)
274+
275+
self.execute_spark(cell, args.output, args.samplemethod, args.maxrows, args.samplefraction, None, coerce)
276+
277+
@cell_magic
278+
@needs_local_scope
279+
@wrap_unexpected_exceptions
280+
@handle_expected_exceptions
281+
def pretty(self, line, cell="", local_ns=None):
282+
"""Evaluates a cell and converts dataframes in cell output to HTML tables."""
283+
if not self._do_not_call_start_session(u""):
284+
return
285+
286+
def pretty_output_handler(out):
287+
if cell_contains_dataframe(out):
288+
self.ipython_display.html(CellOutputHtmlParser.to_html(out))
289+
else:
290+
self.ipython_display.write(out)
291+
292+
so = SparkOutputHandler(html=self.ipython_display.html,
293+
text=pretty_output_handler,
294+
default=self.ipython_display.display)
295+
296+
self.execute_spark(cell, None, None, None, None, None, None, output_handler=so)
265297

266-
self.execute_spark(cell, args.output, args.samplemethod, args.maxrows, args.samplefraction, None, coerce)
267-
else:
268-
return
269298

270299
@magic_arguments()
271300
@cell_magic
@@ -282,15 +311,15 @@ def spark(self, line, cell="", local_ns=None):
282311
@wrap_unexpected_exceptions
283312
@handle_expected_exceptions
284313
def sql(self, line, cell="", local_ns=None):
285-
if self._do_not_call_start_session(""):
286-
args = parse_argstring_or_throw(self.sql, line)
314+
if not self._do_not_call_start_session(""):
315+
return
287316

288-
coerce = get_coerce_value(args.coerce)
317+
args = parse_argstring_or_throw(self.sql, line)
289318

290-
return self.execute_sqlquery(cell, args.samplemethod, args.maxrows, args.samplefraction,
291-
None, args.output, args.quiet, coerce)
292-
else:
293-
return
319+
coerce = get_coerce_value(args.coerce)
320+
321+
return self.execute_sqlquery(cell, args.samplemethod, args.maxrows, args.samplefraction,
322+
None, args.output, args.quiet, coerce)
294323

295324
@magic_arguments()
296325
@cell_magic
@@ -347,17 +376,19 @@ def _do_not_call_start_session(self, line, cell="", local_ns=None):
347376
# No need to add the handle_expected_exceptions decorator to this since we manually catch all
348377
# exceptions when starting the session.
349378

350-
if self.fatal_error:
379+
if self.fatal_error and not self.allow_retry_fatal:
351380
self.ipython_display.send_error(self.fatal_error_message)
352381
return False
353382

354383
if not self.session_started:
355384
skip = False
356385
properties = conf.get_session_properties(self.language)
357-
self.session_started = True
358386

359387
try:
360388
self.spark_controller.add_session(self.session_name, self.endpoint, skip, properties)
389+
self.session_started = True
390+
self.fatal_error = False
391+
self.fatal_error_message = u""
361392
except Exception as e:
362393
self.fatal_error = True
363394
self.fatal_error_message = conf.fatal_error_suggestion().format(e)
@@ -371,6 +402,11 @@ def _do_not_call_start_session(self, line, cell="", local_ns=None):
371402

372403
return self.session_started
373404

405+
@cell_magic
406+
def _do_not_call_allow_retry_fatal(self, line, cell="", local_ns=None):
407+
# enable the flag to retry session creation
408+
self.allow_retry_fatal = True
409+
374410
@cell_magic
375411
@handle_expected_exceptions
376412
def _do_not_call_delete_session(self, line, cell="", local_ns=None):

sparkmagic/sparkmagic/livyclientlib/livysession.py

+3
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ def endpoint(self):
220220
def is_final_status(status):
221221
return status in constants.FINAL_STATUS
222222

223+
def is_posted(self):
224+
return self.status != constants.NOT_STARTED_SESSION_STATUS
225+
223226
def delete(self):
224227
session_id = self.id
225228
self._spark_events.emit_session_deletion_start_event(self.guid, self.kind, session_id, self.status)

sparkmagic/sparkmagic/livyclientlib/sparkcontroller.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,16 @@ def add_session(self, name, endpoint, skip_if_exists, properties):
8787
return
8888
http_client = self._http_client(endpoint)
8989
session = self._livy_session(http_client, properties, self.ipython_display)
90-
self.session_manager.add_session(name, session)
91-
session.start()
90+
91+
try:
92+
session.start()
93+
except:
94+
if session.is_posted():
95+
session.delete()
96+
raise
97+
else:
98+
self.session_manager.add_session(name, session)
99+
92100

93101
def get_session_id_for_client(self, name):
94102
return self.session_manager.get_session_id_for_client(name)

0 commit comments

Comments
 (0)