Skip to content

Commit 142949e

Browse files
authored
Merge pull request #6 from gnestor/develop
0.17.0
2 parents f04a50e + fb93519 commit 142949e

File tree

23 files changed

+471
-246
lines changed

23 files changed

+471
-246
lines changed

.cookiecutter.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
default_context:
2+
author_name: "Grant Nestor"
3+
author_email: "grantnestor@gmail.com"
4+
mime_type: "application/vnd.dataresource+json"
5+
file_extension: "table.json"
6+
mime_short_name: "JSONTable"
7+
extension_name: "jupyterlab_table"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A JupyterLab and Jupyter Notebook extension for rendering [JSON Table Schema](ht
66

77
## Prerequisites
88

9-
* JupyterLab ^0.16.0 and/or Notebook >=4.3.0
9+
* JupyterLab ^0.17.0 and/or Notebook >=4.3.0
1010

1111
## Usage
1212

RELEASE.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,24 @@ This document guides an extension maintainer through creating and publishing a r
44

55
## Update version number
66

7-
Update the version number in `setup.py` and in `package.json`.
7+
Update the version number in `setup.py`, `labextension/package.json`, and `nbextension/package.json`.
8+
9+
Commit your changes, add git tag for this version, and push both commit and tag to your origin/remote repo.
810

911
## Remove generated files
1012

11-
Remove old Javascript bundle builds and delete the `dist/` folder to remove old Python package builds:
13+
Remove old Javascript bundle and Python package builds:
1214

1315
```bash
14-
npm run clean
15-
rm -rf dist/
16+
git clean -xfd
1617
```
1718

1819
## Build the package
1920

2021
Build the Javascript extension bundle, then build the Python package and wheel:
2122

2223
```bash
23-
npm run build
24+
bash build.js
2425
python setup.py sdist
2526
python setup.py bdist_wheel --universal
2627
```

component/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "react-json-table",
2+
"name": "jupyterlab_table_react",
33
"version": "1.0.0",
44
"description": "A React component for rendering JSON schema table",
55
"main": "index.js",

jupyterlab_table/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@ Single Python package for lab and notebook extensions
55
## Structure
66

77
* `static`: Built Javascript from `../labextension/` and `../nbextension/`
8-
* `__init__.py`: Exports paths for lab and notebook extensions and optional display method
8+
* `__init__.py`: Exports paths and metadata of lab and notebook extensions and exports an optional `display` method that can be imported into a notebook and used to easily display data using this renderer

jupyterlab_table/__init__.py

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from IPython.display import display
2-
import pandas as pd
1+
from IPython.display import display, DisplayObject
32
import json
3+
import pandas as pd
4+
from .utils import prepare_data
45

56

67
# Running `npm run build` will create static resources in the static
@@ -22,28 +23,82 @@ def _jupyter_nbextension_paths():
2223
}]
2324

2425

25-
# A display function that can be used within a notebook. E.g.:
26+
# A display class that can be used within a notebook. E.g.:
2627
# from jupyterlab_table import JSONTable
27-
# JSONTable(data)
28-
29-
def JSONTable(data, schema=None):
30-
if isinstance(data, pd.DataFrame):
31-
# hack until pandas supports `df.to_json(orient='json_table_schema')`
32-
# https://github.com/pandas-dev/pandas/pull/14904
33-
text = data.to_csv()
34-
data = [data.loc[i].to_dict() for i in data.index]
35-
else:
36-
text = json.dumps(data, indent=4)
37-
bundle = {
38-
'application/vnd.dataresource+json': {
39-
'resources': [
40-
{
41-
'schema': schema,
42-
'data': data
43-
}
44-
]
45-
},
46-
'application/json': data,
47-
'text/plain': text
48-
}
49-
display(bundle, raw=True)
28+
# JSONTable(data, schema)
29+
30+
class JSONTable(DisplayObject):
31+
"""A display class for displaying JSONTable visualizations in the Jupyter Notebook and IPython kernel.
32+
33+
JSONTable expects a JSON-able list, not serialized JSON strings.
34+
35+
Scalar types (None, number, string) are not allowed, only dict containers.
36+
"""
37+
# wrap data in a property, which warns about passing already-serialized JSON
38+
_data = None
39+
_schema = None
40+
def __init__(self, data=None, schema=None, url=None, filename=None, metadata=None):
41+
"""Create a JSON Table display object given raw data.
42+
43+
Parameters
44+
----------
45+
data : list
46+
Not an already-serialized JSON string.
47+
Scalar types (None, number, string) are not allowed, only list containers.
48+
schema : dict
49+
JSON Table Schema. See http://frictionlessdata.io/guides/json-table-schema/.
50+
url : unicode
51+
A URL to download the data from.
52+
filename : unicode
53+
Path to a local file to load the data from.
54+
metadata: dict
55+
Specify extra metadata to attach to the json display object.
56+
"""
57+
self.schema = schema
58+
self.metadata = metadata
59+
super(JSONTable, self).__init__(data=data, url=url, filename=filename)
60+
61+
def _check_data(self):
62+
if self.data is not None and not isinstance(self.data, (list, pd.DataFrame)):
63+
raise TypeError("%s expects a JSONable list or pandas DataFrame, not %r" % (self.__class__.__name__, self.data))
64+
if self.schema is not None and not isinstance(self.schema, dict):
65+
raise TypeError("%s expects a JSONable dict, not %r" % (self.__class__.__name__, self.schema))
66+
67+
@property
68+
def data(self):
69+
return self._data
70+
71+
@property
72+
def schema(self):
73+
return self._schema
74+
75+
@data.setter
76+
def data(self, data):
77+
if isinstance(data, str):
78+
# warnings.warn("JSONTable expects JSON-able dict or list, not JSON strings")
79+
data = json.loads(data)
80+
self._data = data
81+
82+
@schema.setter
83+
def schema(self, schema):
84+
if isinstance(schema, str):
85+
# warnings.warn("JSONTable expects a JSON-able list, not JSON strings")
86+
schema = json.loads(schema)
87+
self._schema = schema
88+
89+
def _ipython_display_(self):
90+
bundle = {
91+
'application/vnd.dataresource+json': {
92+
'resources': [
93+
{
94+
'schema': self.schema,
95+
'data': prepare_data(self.data)
96+
}
97+
]
98+
},
99+
'text/plain': '<jupyterlab_table.JSONTable object>'
100+
}
101+
metadata = {
102+
'application/vnd.dataresource+json': self.metadata
103+
}
104+
display(bundle, metadata=metadata, raw=True)

jupyterlab_table/utils.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import cgi
2+
import codecs
3+
import collections
4+
import os.path
5+
import pandas as pd
6+
7+
8+
def nested_update(d, u):
9+
"""Update nested dictionary d (in-place) with keys from u."""
10+
for k, v in u.items():
11+
if isinstance(v, collections.Mapping):
12+
d[k] = nested_update(d.get(k, {}), v)
13+
else:
14+
d[k] = v
15+
return d
16+
17+
18+
def abs_path(path):
19+
"""Make path absolute."""
20+
return os.path.join(
21+
os.path.dirname(os.path.abspath(__file__)),
22+
path)
23+
24+
25+
def get_content(path):
26+
"""Get content of file."""
27+
with codecs.open(abs_path(path), encoding='utf-8') as f:
28+
return f.read()
29+
30+
31+
def escape(string):
32+
"""Escape the string."""
33+
return cgi.escape(string, quote=True)
34+
35+
36+
def sanitize_dataframe(df):
37+
"""Sanitize a DataFrame to prepare it for serialization.
38+
39+
* Make a copy
40+
* Raise ValueError if it has a hierarchical index.
41+
* Convert categoricals to strings.
42+
* Convert np.int dtypes to Python int objects
43+
* Convert floats to objects and replace NaNs by None.
44+
* Convert DateTime dtypes into appropriate string representations
45+
"""
46+
import pandas as pd
47+
import numpy as np
48+
49+
df = df.copy()
50+
51+
if isinstance(df.index, pd.core.index.MultiIndex):
52+
raise ValueError('Hierarchical indices not supported')
53+
if isinstance(df.columns, pd.core.index.MultiIndex):
54+
raise ValueError('Hierarchical indices not supported')
55+
56+
for col_name, dtype in df.dtypes.iteritems():
57+
if str(dtype) == 'category':
58+
# XXXX: work around bug in to_json for categorical types
59+
# https://github.com/pydata/pandas/issues/10778
60+
df[col_name] = df[col_name].astype(str)
61+
elif np.issubdtype(dtype, np.integer):
62+
# convert integers to objects; np.int is not JSON serializable
63+
df[col_name] = df[col_name].astype(object)
64+
elif np.issubdtype(dtype, np.floating):
65+
# For floats, convert nan->None: np.float is not JSON serializable
66+
col = df[col_name].astype(object)
67+
df[col_name] = col.where(col.notnull(), None)
68+
elif str(dtype).startswith('datetime'):
69+
# Convert datetimes to strings
70+
# astype(str) will choose the appropriate resolution
71+
df[col_name] = df[col_name].astype(str).replace('NaT', '')
72+
return df
73+
74+
75+
def prepare_data(data=None):
76+
"""Prepare Plotly data from Pandas DataFrame."""
77+
78+
if isinstance(data, list):
79+
return data
80+
data = sanitize_dataframe(data)
81+
return data.to_dict(orient='records')

labextension/README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ A JupyterLab extension for rendering JSON Table Schema
44

55
## Prerequisites
66

7-
* `jupyterlab>=0.11`
8-
9-
![file renderer](http://g.recordit.co/cbf0xnQHKn.gif)
7+
* `jupyterlab@^0.17.0`
108

119
## Development
1210

labextension/build_extension.js

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,45 @@ var path = require('path');
33

44
buildExtension({
55
name: 'jupyterlab_table',
6-
entry: './src/plugin.js',
7-
outputDir: '../jupyterlab_table/static',
6+
entry: path.join(__dirname, 'src', 'plugin.js'),
7+
outputDir: path.join(
8+
__dirname,
9+
'..',
10+
'jupyterlab_table',
11+
'static'
12+
),
813
useDefaultLoaders: false,
914
config: {
1015
module: {
1116
loaders: [
1217
{ test: /\.html$/, loader: 'file-loader' },
1318
{ test: /\.(jpg|png|gif)$/, loader: 'file-loader' },
14-
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
15-
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
16-
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },
19+
{
20+
test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
21+
loader: 'url-loader?limit=10000&mimetype=application/font-woff'
22+
},
23+
{
24+
test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
25+
loader: 'url-loader?limit=10000&mimetype=application/font-woff'
26+
},
27+
{
28+
test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
29+
loader: 'url-loader?limit=10000&mimetype=application/octet-stream'
30+
},
1731
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
18-
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' },
32+
{
33+
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
34+
loader: 'url-loader?limit=10000&mimetype=image/svg+xml'
35+
},
1936
{ test: /\.json$/, loader: 'json-loader' },
20-
{ test: /\.js$/,
21-
exclude: /node_modules(?!\/react-json-table)/,
37+
{
38+
test: /\.js$/,
39+
include: [
40+
path.join(__dirname, 'src'),
41+
path.join(__dirname, 'node_modules', 'jupyterlab_table_react')
42+
],
2243
loader: 'babel-loader',
23-
query: {
24-
presets: ['latest', 'stage-0', 'react']
25-
}
44+
query: { presets: [ 'latest', 'stage-0', 'react' ] }
2645
}
2746
]
2847
}

labextension/package.json

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"private": true,
33
"name": "jupyterlab_table_labextension",
4-
"version": "0.16.0",
4+
"version": "0.17.0",
5+
"author": "Grant Nestor <grantnestor@gmail.com>",
56
"description": "A JupyterLab extension for rendering JSON Table Schema",
6-
"author": "Grant Nestor",
77
"main": "lib/plugin.js",
88
"keywords": [
99
"jupyter",
@@ -12,7 +12,7 @@
1212
],
1313
"scripts": {
1414
"build": "node build_extension.js",
15-
"watch": "watch \"npm install\" src ../component --wait 15 --ignoreDotFiles",
15+
"watch": "watch \"npm install\" src ../component --wait 10 --ignoreDotFiles",
1616
"preinstall": "npm install ../component",
1717
"prepublish": "npm run build",
1818
"extension:install": "jupyter labextension install --symlink --py --sys-prefix jupyterlab_table",
@@ -21,20 +21,19 @@
2121
"extension:disable": "jupyter labextension disable --py --sys-prefix jupyterlab_table"
2222
},
2323
"dependencies": {
24-
"jupyterlab": "^0.16.0",
25-
"phosphor": "^0.7.0",
24+
"jupyterlab": "^0.17.0",
25+
"@phosphor/algorithm": "^0.1.0",
26+
"@phosphor/widgets": "^0.1.3",
2627
"react": "^15.3.2",
2728
"react-dom": "^15.3.2"
2829
},
2930
"devDependencies": {
3031
"@jupyterlab/extension-builder": ">=0.10.0",
31-
"@jupyterlab/services": ">=0.25.0",
3232
"babel-core": "^6.18.2",
3333
"babel-loader": "^6.2.7",
3434
"babel-preset-latest": "^6.16.0",
3535
"babel-preset-react": "^6.16.0",
3636
"babel-preset-stage-0": "^6.16.0",
37-
"rimraf": "^2.5.4",
3837
"watch": "^1.0.1"
3938
}
4039
}

0 commit comments

Comments
 (0)