Skip to content

Commit

Permalink
Merge pull request #549 from great-expectations/feature/render-in-not…
Browse files Browse the repository at this point in the history
…ebooks

Feature/render in notebooks
  • Loading branch information
eugmandel committed Jul 19, 2019
2 parents 3ce7b1a + 398c0e3 commit d1100cc
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 4 deletions.
148 changes: 147 additions & 1 deletion great_expectations/jupyter_ux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
"""Utility functions for working with great_expectations within jupyter notebooks or jupyter lab.
"""

import json
import os
import logging
import great_expectations as ge
import great_expectations.render as render
from datetime import datetime

import tzlocal
from IPython.core.display import display, HTML

def set_data_source(context, data_source_type=None):
"""
TODO: Needs a docstring and tests.
"""

data_source_name = None

if not data_source_type:
Expand Down Expand Up @@ -67,6 +75,10 @@ def set_data_source(context, data_source_type=None):
return data_source_name

def setup_notebook_logging(logger=None):
"""
TODO: Needs a docstring and tests.
"""

def posix2local(timestamp, tz=tzlocal.get_localzone()):
"""Seconds since the epoch -> local time as an aware datetime object."""
return datetime.fromtimestamp(timestamp, tz)
Expand Down Expand Up @@ -99,6 +111,10 @@ def formatTime(self, record, datefmt=None):
warnings.filterwarnings('ignore')

def list_available_data_asset_names(context, data_source_name=None):
"""
TODO: Needs a docstring and tests.
"""

datasources = context.list_datasources()
for datasource in datasources:
if data_source_name and datasource['name'] != data_source_name:
Expand All @@ -125,4 +141,134 @@ def list_available_data_asset_names(context, data_source_name=None):
</p>
"""))

#TODO: add expectation suite names (existing)
#TODO: add expectation suite names (existing)

bootstrap_link_element = """<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">"""
cooltip_style_element = """<style type="text/css">
.cooltip {
display:inline-block;
position:relative;
text-align:left;
}
.cooltip .top {
min-width:200px;
top:-6px;
left:50%;
transform:translate(-50%, -100%);
padding:10px 20px;
color:#FFFFFF;
background-color:#222222;
font-weight:normal;
font-size:13px;
border-radius:8px;
position:absolute;
z-index:99999999;
box-sizing:border-box;
box-shadow:0 1px 8px rgba(0,0,0,0.5);
display:none;
}
.cooltip:hover .top {
display:block;
}
.cooltip .top i {
position:absolute;
top:100%;
left:50%;
margin-left:-12px;
width:24px;
height:12px;
overflow:hidden;
}
.cooltip .top i::after {
content:'';
position:absolute;
width:12px;
height:12px;
left:50%;
transform:translate(-50%,-50%) rotate(45deg);
background-color:#222222;
box-shadow:0 1px 8px rgba(0,0,0,0.5);
}
</style>
"""

def display_column_expectations_as_section(
expectation_suite,
column,
section_renderer=render.renderer.column_section_renderer.PrescriptiveColumnSectionRenderer,
view_renderer=render.view.view.DefaultJinjaSectionView,
include_styling=True,
return_without_displaying=False,
):
"""This is a utility function to render all of the Expectations in an ExpectationSuite with the same column name as an HTML block.
By default, the HTML block is rendered using PrescriptiveColumnSectionRenderer and the view is rendered using DefaultJinjaSectionView.
Therefore, it should look exactly the same as the default renderer for build_docs.
Example usage:
exp = context.get_expectation_suite("notable_works_by_charles_dickens", "BasicDatasetProfiler")
display_column_expectations_as_section(exp, "Type")
"""

#TODO: replace this with a generic utility function, preferably a method on an ExpectationSuite class
column_expectation_list = [ e for e in expectation_suite["expectations"] if "column" in e["kwargs"] and e["kwargs"]["column"] == column ]

#TODO: Handle the case where zero evrs match the column name

document = render.renderer.PrescriptiveColumnSectionRenderer.render(column_expectation_list)
view = render.view.DefaultJinjaSectionView.render({
"section": document,
"section_loop": {"index": 1},
})

if include_styling:
html_to_display = bootstrap_link_element+cooltip_style_element+view
else:
html_to_display = view

if return_without_displaying:
return html_to_display
else:
display(HTML(html_to_display))

def display_column_evrs_as_section(
evrs,
column,
section_renderer=render.renderer.column_section_renderer.DescriptiveColumnSectionRenderer,
view_renderer=render.view.view.DefaultJinjaSectionView,
include_styling=True,
return_without_displaying=False,
):
"""This is a utility function to render all of the EVRs in an ExpectationSuite with the same column name as an HTML block.
By default, the HTML block is rendered using PrescriptiveColumnSectionRenderer and the view is rendered using DefaultJinjaSectionView.
Therefore, it should look exactly the same as the default renderer for build_docs.
Example usage:
display_column_evrs_as_section(exp, "my_column")
"""

#TODO: replace this with a generic utility function, preferably a method on an ExpectationSuite class
column_evr_list = [ e for e in evrs["results"] if "column" in e["expectation_config"]["kwargs"] and e["expectation_config"]["kwargs"]["column"] == column ]

#TODO: Handle the case where zero evrs match the column name

document = render.renderer.DescriptiveColumnSectionRenderer.render(column_evr_list)
view = render.view.DefaultJinjaSectionView.render({
"section": document,
"section_loop": {"index": 1},
})

if include_styling:
html_to_display = bootstrap_link_element+cooltip_style_element+view
else:
html_to_display = view

if return_without_displaying:
return html_to_display
else:
display(HTML(html_to_display))
4 changes: 3 additions & 1 deletion great_expectations/render/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from .view import DefaultJinjaPageView
from .view import (
DefaultJinjaPageView,
)
10 changes: 8 additions & 2 deletions great_expectations/render/renderer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
from .column_section_renderer import DescriptiveColumnSectionRenderer, PrescriptiveColumnSectionRenderer
from .page_renderer import DescriptivePageRenderer, PrescriptivePageRenderer
from .column_section_renderer import (
DescriptiveColumnSectionRenderer,
PrescriptiveColumnSectionRenderer,
)
from .page_renderer import (
DescriptivePageRenderer,
PrescriptivePageRenderer,
)
1 change: 1 addition & 0 deletions great_expectations/render/view/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .view import (
DefaultJinjaPageView,
DefaultJinjaIndexPageView,
DefaultJinjaSectionView,
)
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ mock>=3.0.5
pytest-cov>=2.6.1
coveralls>=1.3
altair>=3.1.0
tzlocal>=1.2
135 changes: 135 additions & 0 deletions tests/test_jupyter_ux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import great_expectations as ge
import great_expectations.jupyter_ux as jux
from great_expectations.profile.basic_dataset_profiler import BasicDatasetProfiler

def test_styling_elements_exist():
assert "<link" in jux.bootstrap_link_element
assert "bootstrap" in jux.bootstrap_link_element

assert jux.cooltip_style_element[:23] == '<style type="text/css">'
assert ".cooltip" in jux.cooltip_style_element

def test_display_column_expectations_as_section(basic_expectation_suite):
html_to_display = jux.display_column_expectations_as_section(
basic_expectation_suite,
"naturals",
include_styling=False,
return_without_displaying=True
)
print(html_to_display)
assert html_to_display == """\
<div id="section-1" class="ge-section container-fluid">
<div class="row">
<div id="content-block-1" class="col-12" >
<h3 id="content-block-1-header" class="alert alert-secondary" >
naturals
</h3>
</div>
<div id="content-block-2" class="col-12" >
<ul id="content-block-2-body" >
<li >is a required field.</li>
<li >values must be unique.</li>
</ul>
</div>
</div>
</div>"""

html_to_display = jux.display_column_expectations_as_section(
basic_expectation_suite,
"naturals",
return_without_displaying=True
)
print(html_to_display)
assert html_to_display == """\
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"><style type="text/css">
.cooltip {
display:inline-block;
position:relative;
text-align:left;
}
.cooltip .top {
min-width:200px;
top:-6px;
left:50%;
transform:translate(-50%, -100%);
padding:10px 20px;
color:#FFFFFF;
background-color:#222222;
font-weight:normal;
font-size:13px;
border-radius:8px;
position:absolute;
z-index:99999999;
box-sizing:border-box;
box-shadow:0 1px 8px rgba(0,0,0,0.5);
display:none;
}
.cooltip:hover .top {
display:block;
}
.cooltip .top i {
position:absolute;
top:100%;
left:50%;
margin-left:-12px;
width:24px;
height:12px;
overflow:hidden;
}
.cooltip .top i::after {
content:'';
position:absolute;
width:12px;
height:12px;
left:50%;
transform:translate(-50%,-50%) rotate(45deg);
background-color:#222222;
box-shadow:0 1px 8px rgba(0,0,0,0.5);
}
</style>
<div id="section-1" class="ge-section container-fluid">
<div class="row">
<div id="content-block-1" class="col-12" >
<h3 id="content-block-1-header" class="alert alert-secondary" >
naturals
</h3>
</div>
<div id="content-block-2" class="col-12" >
<ul id="content-block-2-body" >
<li >is a required field.</li>
<li >values must be unique.</li>
</ul>
</div>
</div>
</div>"""

def test_display_column_evrs_as_section():
#TODO: We should add a fixture that contains EVRs
df = ge.read_csv("./tests/test_sets/Titanic.csv")
df.profile(BasicDatasetProfiler)
evrs = df.validate(result_format="SUMMARY") # ["results"]

html_to_display = jux.display_column_evrs_as_section(
evrs,
"Name",
include_styling=False,
return_without_displaying=True
)
print(html_to_display)

#FIXME: This isn't a full snapshot test.
assert '<div id="section-1" class="ge-section container-fluid">' in html_to_display
assert '<span class="badge badge-info" >Carlsson, Mr Frans Olof</span>' in html_to_display
assert '<li class="list-group-item d-flex justify-content-between align-items-center" >expect_column_values_to_be_in_type_list <span class="badge badge-secondary badge-pill" >True</span></li>' in html_to_display

0 comments on commit d1100cc

Please sign in to comment.