Skip to content

Commit 5d86ef4

Browse files
committed
Merge pull request plotly#128 from plotly/grid_api
grid api
2 parents 5761bcf + 44120ec commit 5d86ef4

File tree

21 files changed

+1147
-28
lines changed

21 files changed

+1147
-28
lines changed

gridspec.md

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
### Creating a grid with `grid_objs`
2+
3+
```python
4+
from plotly.grid_objs import Column, Grid
5+
6+
column_1 = Column([1, 2, 3], 'column 1')
7+
8+
column_2 = Column(['a', 'b', datetime.datetime.now()], 'column 2')
9+
10+
grid = Grid(column_1, column_2)
11+
12+
unique_url = py.grid_ops.upload(grid, filename, world_readable=True)
13+
```
14+
15+
### Updating grids
16+
17+
Grids are identified with either `grid` or `grid_url`, or `filename`
18+
`filename` will be unsupported in this version
19+
```python
20+
21+
rows = [[1, 'a'], [2, 'b']]
22+
23+
grid = Grid(c1, c2)
24+
25+
py.grid_ops.upload(grid, 'my file')
26+
27+
# We recommend this call signature, since `row` gets appended to the grid
28+
py.grid_ops.append_rows(rows, grid=grid)
29+
30+
# But, these all do the same thing
31+
py.grid_ops.append_rows(rows, grid_url="https://plot.ly/~chris/3") #shortcut
32+
py.grid_ops.append_rows(rows, filename='my file') # currently unsupported.
33+
# will do a get request behind
34+
# the scenes to get the grid_id
35+
```
36+
37+
Similarly for appending columns:
38+
```python
39+
from plotly.grid_objs import Column
40+
41+
new_col = Column([1,2,3], 'new col name')
42+
43+
# these are equivalent
44+
py.grid_ops.append_columns([new_col], grid_url='https://plot.ly/~chris/3')
45+
py.grid_ops.append_columns([new_col], filename='my file') # Currently unsupported
46+
47+
# this, too:
48+
grid = Grid(Column([1,2,3], 'first column name'))
49+
py.grid_ops.upload(grid, 'my file')
50+
51+
py.grid_ops.append_columns([new_col], grid=grid) # also implicitly adds new_col to grid
52+
53+
grid[0].name # 'first column name'
54+
grid[1].name # 'new col name'
55+
```
56+
57+
58+
### On overwriting and duplicate file names and deletion
59+
60+
Overwriting currently isn't possible. For now,
61+
```python
62+
>> py.grid_ops.upload(grid, 'my grid')
63+
"PlotlyFileException: Yikes, a file already exists with this filename."
64+
"You can delete that file with:"
65+
"> py.grid_ops.delete('my grid')"
66+
"Warning: If any graphs were made from this grid, the data in those graphs"
67+
"will also be lost. If you make a new grid after you delete with the same filename, "
68+
"the new grid's URL will also be different."
69+
"That's confusing and you're probably not having a good time.""
70+
"Questions? [email protected]"
71+
```
72+
73+
In the near future:
74+
```python
75+
# Updates the data, but not the column ids, of the grid. Referenced plots don't break.
76+
# Behind the scenes, this:
77+
# 1 - Makes a `GET` request to retrieve a {column_name: column_id} hash
78+
# 2 - Makes a `PUT` request to update the data of the columns
79+
>> py.grid_ops.upload('my grid') # overwrite defaults to True
80+
81+
# Or, recieve an exception with:
82+
>> py.grid_ops.upload(grid, 'my grid', overwrite=False)
83+
"PLotlyFileException: Yikes! ..."
84+
```
85+
86+
Deleting:
87+
88+
```
89+
# throw good errors if none or more than 1 were specified
90+
py.grid_ops.delete(filename=None, grid_url=None, grid=None, grid_id=None)
91+
```
92+
93+
In the future, once we can delete Plots and Folders
94+
95+
```
96+
py.file_ops.delete(file_path=None, fid=None, url=None)
97+
```
98+
99+
100+
### Appearance and Access
101+
102+
```python
103+
>> print Column([1,2,3], 'column 1')
104+
<Column "column 1": [1, 2, 3]>
105+
```
106+
107+
```python
108+
>> print Grid(col1, col2)
109+
<Grid: [<Column "column 1": [1, 2, 3]>, <Column "column 2": ["a", "b", "c"]>]>
110+
```
111+
112+
```python
113+
>> grid = Grid(col1, col2)
114+
>> print grid[0]
115+
<Column "column 1": [1, 2, 3]>
116+
```
117+
118+
```python
119+
>> grid = Grid(col1, col2)
120+
>> print grid.get_column('column 1')
121+
<Column "column 1": [1, 2, 3]>
122+
```
123+
124+
### Creating a graph from a grid
125+
126+
If you have the grid
127+
```python
128+
>> from plotly.grid_objs import Grid
129+
>> grid = Grid(column_1, column_2)
130+
>> grid.upload(grid, 'experimental data')
131+
132+
>> fig_data = [Scatter(xsrc=grid[0], ysrc=grid[0])]
133+
>> print Scatter(xsrc=grid[0], ysrc=grid[1])
134+
[{"xsrc": "chris/8:3dkb", "ysrc": "chris/8:cbk8", "type": "scatter"}]
135+
>> py.plot(fig_data)
136+
137+
>> Scatter(x=[1,2,3], y=[2,1,2])
138+
"High five!"
139+
>> Scatter(xsrc=[1,2,3], ysrc=[2,1,2])
140+
"PlotlyTypeException: xrc and ysrc must be type string or plotly.grid_obj.Column"
141+
>> Scatter(xsrc=Column('myCol', [1,2,3]), ysrc=Column('otherCol', [2,1,2]))
142+
"PlotlyFileException: you must upload a grid before you can reference it in plots"
143+
>> Scatter(xsrc=localGrid[0], ysrc=localGrid[1])
144+
"PlotlyFileException: you must upload a grid before you can reference it in plots"
145+
>> Scatter(x=grid[0], y=grid[1])
146+
"PlotlyTypeException: Yikes, column objects aren't currently supported here."
147+
"x must be an array of strings, numbers, or datetimes."
148+
>> print Scatter(xsrc=grid[0], yscr=grid[1])
149+
{"xsrc": "chris/3:3dfbk", "ysrc": "chris/3:dk3c"}
150+
```
151+
152+
Otherwise, download the grid (Not currently supported)
153+
```
154+
>> grid = grid_ops.get(filename=None, grid_id=None, grid_url=None)
155+
```
156+
157+
(Download should use same endpoint as `grid_url.json`, e.g. [https://plot.ly/~chris/142.json](https://plot.ly/~chris/142.json))
158+
159+
### Errors
160+
```python
161+
>> grid = Grid(column_1, column_2)
162+
>> trace = Scatter(x=grid[0], y=grid[1])
163+
"PlotlyGridException: Grid must be uploaded to Plotly before figures can be created."
164+
"Call `py.grid_ops.upload(grid)`"
165+
```
166+
167+
```python
168+
>> col1 = Column([], 'my column name')
169+
>> col2 = Column([], 'my column name')
170+
>> Grid(col1, col2)
171+
"PlotlyGridException: Grid can't have duplicate column names"
172+
```
173+
174+
```python
175+
>> py.grid_ops.append_row(Row({'column 1': 1}), grid=grid)
176+
"PlotlyGridException: Missing column entries, partial row update is not supported."
177+
"Supply data for 'column 2' and try again."
178+
```
179+
180+
Type checking boiler plate
181+
```python
182+
>> Column({'a': 'b'}, 'col1')
183+
"PlotlyColumnException: Data argument must be an array of numbers, strings, Nones, or datetimes"
184+
```
185+
186+
```python
187+
>> Column([{'a': 'b'}], 'col1')
188+
"PlotlyColumnException: Data values must be an array string, a number, Nones, or a datetime"
189+
```
190+
191+
### Exceptions from Requests
192+
A `PlotlyRequestError` that prints a useful error message from the server:
193+
1. Print `response.detail` if provided (Plotly error message)
194+
2. Otherwise, print `response.body` if the response is plain-text
195+
3. Otherwise, print the original `requests.expceptions.HTTPError` error message.
196+
197+
Also, store the status code.
198+
199+
200+
### Adding metadata to grids
201+
202+
```python
203+
c1 = Column('first column', [1, 2, 3, 4])
204+
grid = Grid([c1])
205+
meta = {"settings":{"scope1":{"model":"Unicorn Finder","voltage":4}}}
206+
py.grid_ops.upload(
207+
grid,
208+
unique_filename,
209+
meta=meta,
210+
auto_open=False)
211+
```
212+
213+
```python
214+
# First, create a grid
215+
c1 = Column('first column', [1, 2, 3, 4])
216+
grid = Grid([c1])
217+
grid_url = py.grid_ops.upload(grid, unique_filename, auto_open=False)
218+
219+
# Add some meta data to that grid
220+
meta = {"settings": {"scope1": {"model": "Unicorn Finder", "voltage": 4}}}
221+
meta_url = py.meta_ops.upload(
222+
meta,
223+
grid_url=grid_url)
224+
```
225+
226+
### Plotly File system
227+
228+
```python
229+
230+
>> py.file_ops.mkdirs('new folder in root')
231+
232+
>> py.file_ops.mkdirs('make/each/of/these/folders')
233+
```
234+
235+
Note that this is like `mkdir -p`. `mkdirs` is a Java convention.
236+
We could also use our own, like:
237+
238+
- `py.file_ops.create_folders('new/folders')`
239+
- `py.file_ops.new_folders('new/folders')`
240+
241+
These commands will:
242+
- return status codes: `200` if nothing created, `201` if created
243+
- raise exception if a file or folder already exists with that path
244+
245+
246+
In the future, once we can delete Plots and Folders
247+
248+
```
249+
py.file_ops.delete(file_path=None, fid=None, url=None)
250+
```
251+
252+
Or, if we want to keep unix convention (do we?)
253+
```
254+
py.file_ops.rm(file_path=None, fid=None, url=None)
255+
```

plotly/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@
2828

2929
from __future__ import absolute_import
3030

31-
from plotly import plotly, graph_objs, tools, utils
31+
from plotly import plotly, graph_objs, grid_objs, tools, utils
3232
from plotly.version import __version__

plotly/exceptions.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,59 @@
1515
1616
1717
"""
18-
18+
import json
1919

2020
## Base Plotly Error ##
2121

2222
class PlotlyError(Exception):
2323
pass
2424

2525

26+
27+
class InputError(PlotlyError):
28+
pass
29+
30+
31+
class PlotlyRequestError(PlotlyError):
32+
def __init__(self, requests_exception):
33+
self.status_code = requests_exception.response.status_code
34+
self.HTTPError = requests_exception
35+
content_type = requests_exception.response.headers['content-type']
36+
if 'json' in content_type:
37+
content = requests_exception.response.content
38+
if content != '':
39+
res_payload = json.loads(requests_exception.response.content)
40+
if 'detail' in res_payload:
41+
self.message = res_payload['detail']
42+
else:
43+
self.message = ''
44+
else:
45+
self.message = ''
46+
elif content_type == 'text/plain':
47+
self.message = requests_exception.response.content
48+
else:
49+
self.message = requests_exception.message
50+
51+
def __str__(self):
52+
return self.message
53+
54+
55+
## Grid Errors ##
56+
57+
COLUMN_NOT_YET_UPLOADED_MESSAGE = (
58+
"Hm... it looks like your column '{column_name}' hasn't "
59+
"been uploaded to Plotly yet. You need to upload your "
60+
"column to Plotly before you can assign it to '{reference}'.\n"
61+
"To upload, try `plotly.plotly.grid_objs.upload` or "
62+
"`plotly.plotly.grid_objs.append_column`.\n"
63+
"Questions? [email protected]"
64+
)
65+
66+
NON_UNIQUE_COLUMN_MESSAGE = (
67+
"Yikes, plotly grids currently "
68+
"can't have duplicate column names. Rename "
69+
"the column \"{}\" and try again."
70+
)
2671
## Would Cause Server Errors ##
2772

2873
class PlotlyEmptyDataError(PlotlyError):

plotly/graph_objs/graph_objs.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@
3030
from plotly.graph_objs.graph_objs_tools import (
3131
INFO, OBJ_MAP, NAME_TO_KEY, KEY_TO_NAME
3232
)
33-
import copy
33+
3434
from plotly import exceptions
35+
from plotly import utils
36+
37+
import copy
3538
import sys
3639
if sys.version[:3] == '2.6':
3740
from ordereddict import OrderedDict
@@ -297,6 +300,11 @@ class PlotlyDict(dict):
297300

298301
def __init__(self, *args, **kwargs):
299302
class_name = self.__class__.__name__
303+
304+
for key in kwargs:
305+
if utils.is_source_key(key):
306+
kwargs[key] = self._assign_id_to_src(key, kwargs[key])
307+
300308
super(PlotlyDict, self).__init__(*args, **kwargs)
301309
if issubclass(NAME_TO_CLASS[class_name], PlotlyTrace):
302310
if (class_name != 'PlotlyTrace') and (class_name != 'Trace'):
@@ -307,6 +315,37 @@ def __init__(self, *args, **kwargs):
307315
"dictionary-like graph_objs.\nIt is not meant to be "
308316
"a user interface.")
309317

318+
def __setitem__(self, key, value):
319+
320+
if key in ('xsrc', 'ysrc'):
321+
value = self._assign_id_to_src(key, value)
322+
323+
return super(PlotlyDict, self).__setitem__(key, value)
324+
325+
def _assign_id_to_src(self, src_name, src_value):
326+
if isinstance(src_value, basestring):
327+
src_id = src_value
328+
else:
329+
try:
330+
src_id = src_value.id
331+
except:
332+
err = ("{} does not have an `id` property. "
333+
"{} needs to be assigned to either an "
334+
"object with an `id` (like a "
335+
"plotly.grid_objs.Column) or a string. "
336+
"The `id` is a unique identifier "
337+
"assigned by the Plotly webserver "
338+
"to this grid column.")
339+
src_value_str = str(src_value)
340+
err = err.format(src_name, src_value_str)
341+
raise exceptions.InputError(err)
342+
343+
if src_id == '':
344+
err = exceptions.COLUMN_NOT_YET_UPLOADED_MESSAGE
345+
err.format(column_name=src_value.name, reference=src_name)
346+
raise exceptions.InputError(err)
347+
return src_id
348+
310349
def update(self, dict1=None, **dict2):
311350
"""Update current dict with dict1 and then dict2.
312351

plotly/grid_objs/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
""""
2+
grid_objs
3+
=========
4+
5+
"""
6+
7+
8+
from . grid_objs import Grid, Column

0 commit comments

Comments
 (0)