Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embed code generation from Python #1387

Merged
merged 28 commits into from
Jun 29, 2017
Merged

Conversation

vidartf
Copy link
Member

@vidartf vidartf commented May 24, 2017

Adds functions to the Python side API for generating embeddable javascript/HTML representation of the widgets. Based on code from ipyvolume.

Resolves #1241.

TODOs

  • Documentation
  • Tests
  • Figure out source of error: Could not create model: Model name: DescriptionStyleModel that appears regularly for full saves.

@jasongrout
Copy link
Member

I just added that description style model. Happy to help if I can.

@vidartf
Copy link
Member Author

vidartf commented May 24, 2017

Ah, then I'm guessing it is simply the fact that it is not available in the version on the CDN yet.

@jasongrout
Copy link
Member

jasongrout commented May 24, 2017 via email

@maartenbreddels
Copy link
Member

maartenbreddels commented May 24, 2017

Excellent work, glad to see this going upstream. I recommend being able to override the embed url for these kinds of things. Also, would be nice if the html template can be passed as argument to embed_minimal_html.

@vidartf
Copy link
Member Author

vidartf commented May 24, 2017

@maartenbreddels : I figured if someone wanted more custom HTML, they could simply use the snippet code, and then wrap it themselves (embed_minimal_html is mostly intended as a dev tool).

Having the url overridable sounds like a good idea.

@vidartf
Copy link
Member Author

vidartf commented May 24, 2017

Regarding the javascript embed parser: Does the embedded widget views need to be specified in dependency order, or does the order not matter?

@jasongrout
Copy link
Member

Does the embedded widget views need to be specified in dependency order, or does the order not matter?

I'm not quite sure what you mean. What dependencies do you see between views?

@maartenbreddels
Copy link
Member

Order doesn't matter afaik.

@vidartf
Copy link
Member Author

vidartf commented May 24, 2017

What dependencies do you see between views?

@jasongrout Not entirely sure, but I figured there might be some dependencies e.g. with the layout views. It was mostly catalyzed by the warnings I see for the exported minimal HTML: Style not applied because a parent view does not exist (widget_layout.js:116), although that might be related to the DescriptionStyleModel things(?).

Either way I see that the views need a bit further attention, e.g. for the following case:

import ipywidgets
import ipywidgets.widgets.embed as embed

w1 = ipywidgets.IntSlider(min=0, max=100)
w2 = ipywidgets.IntText()
box = ipywidgets.HBox(children=[w1, w2])

embed.embed_minimal_html('test.html', widgets=box)

Here, we definitely want to save the state of w1 and w2, but I don't think it makes sense to add all three views to the export. We should probably only include the views of the widgets specified to the function?

@jasongrout
Copy link
Member

Are all three views added? I would think that just one view should be added, i.e., the top-level widget that is displayed.

@vidartf
Copy link
Member Author

vidartf commented May 24, 2017

The last two commits (15bfcb1 + 01f7923) have fixed this, but see the note in the commit message of 01f7923.

@maartenbreddels
Copy link
Member

Should we have some unittest with it, it doesn't have to do really fancy, but to make sure there is code coverage for everything. Like exporting a single widget, two widgets, and check if a complicated dependency graph is handled correctly?

@vidartf
Copy link
Member Author

vidartf commented May 24, 2017

@maartenbreddels Definitly, it is already in the TODO list on top! It won't happen today though, as I am heading home. If you have an interesting dependency graph to test for, feel free to share!

@maartenbreddels
Copy link
Member

I see, 👍 on the tests and documentation. If you are 'ready', let me know, I can see if I can totally remove embed.py from ipyvolume and rely on this, that would be nice!

@maartenbreddels
Copy link
Member

@vidartf just want to make you aware of what's happening in #1394 . It would make sense this PR would also include a get_embed_url() function right? Then we can also use that instead of what I suggested https://github.com/jupyter-widgets/jupyter-sphinx/pull/13/files

@vidartf
Copy link
Member Author

vidartf commented May 29, 2017

@maartenbreddels Yes, there is a # TODO note for that, so that would be very nice 👍

@vidartf
Copy link
Member Author

vidartf commented May 29, 2017

@maartenbreddels: I removed the code to add referring widgets. The comment in the code mentioned this being useful for layouts, but widgets refer to the layouts, not the other way around, so those should already be included. Instead, this would add widgets to the state that didn't really need to be included. The only exception I could find was links, which I made a special case for (any link between widgets included in the state are also included).

Am I missing any obvious cases where this would make sense to keep?

@vidartf vidartf changed the title [WIP] Embed code generation from Python Embed code generation from Python May 30, 2017
@vidartf
Copy link
Member Author

vidartf commented May 30, 2017

Alright, it should be ready for a full review now. Note that I made the default behavior to include the state of all widgets in order to match the behavior of the notebook interface (and be conservative).

@maartenbreddels
Copy link
Member

Yes, I doubt that comment made sense, and indeed it's needed make jslink work, but there could be other widgets that act similarly. Say a 3rd party link-like-widget.

@jasongrout jasongrout added this to the 7.0 milestone May 30, 2017
if include_all:
state = Widget.get_manager_state(drop_defaults=drop_defaults, widgets=None)['state']
else:
state = dependency_state(views, drop_defaults)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that we should try to walk the widgets dependency graph. We had a similar thing at some point to restrict to widgets that were displayed and spent a lot of time tracking corner cases.

Although I really like your implementation with generators :)

There are some cases where you might miss widgets such as a widget that you are not especially looking at that emits events (e.g. a controller widget).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ipyvolume it can be the difference between a 100mb file and a 10mb file (simply because you've created 10 widgets with 10mb of data). So for ipyvolume it's a must have, but maybe don't do this by default? Or have a argument 'include_all=True/False' orso?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a much better story about cleaning up widgets. It's hard to do automatic garbage collection with the object split across frontend and backend, though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SylvainCorlay How about this:

  • Replace include_all=True with an argument state=None.
  • If state is None, include state of all widgets known to manager.
  • Else if state is a dict, simply use that. This way API consumers can traverse dependencies for their widgets to generate state.
  • Possibly add some helpers for consumers to build such a state dict on their own?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the state=None strategy.

(The issue with the include_all=False it seems to claim that one would always get a functional set of widgets, which we me have missed some that have e.g. a jsdlink to a widget of interest.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote for the inverse of this. state is None should be smart (it seems to work fine right?). Otherwise, try explaining to someone that ALL widgets are by default serialized, when you only want the snippet to show my_widget. If for some reason our code fails, we should fix it, and in the meantime you can bypass it by passing state=Widget.get_manager_state().
I don't like regresssion, let's progress! :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SylvainCorlay The current code does that actually, if that jsdlink is in Widgets.widgets (and every widget should right), it will find it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to only be included if both source and target are in store.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While if only the target of the link is in store, the link should be added to the store, as well as the source of the link...

Then we must start over again with the rexursive wall from the source...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've committed the outlined change, feel free to play around with it if you want to discover corner cases. Some of the most obvious omissions of the automatic dependency finder are now more explicitly stated in the docstring of the dependency_state function.

@vidartf
Copy link
Member Author

vidartf commented Jun 19, 2017

Alright, I cleaned up the code after the reviews. Given that there will definitely be corner cases that the dependency finder will not find (not without a lot of effort on our part), I think it makes sense to leave the default as simply including all state (at least for this PR).

For now, those confident that the simple dependency walker is sufficient, are free to use that to generate a smaller state. Alternatively, users can build their own custom state, if they have particularly difficult cases to handle, and want to avoid the full state.

PS: Some of the JS tests are failing CI, no idea why.

Copy link
Member

@jasongrout jasongrout left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about putting the embed file at the top level, instead of inside the widget directory? So it would be ipywidgets.embed instead of ipywidgets.widgets.embed?

docs/source/embedding.md Outdated Show resolved Hide resolved
docs/source/embedding.md Outdated Show resolved Hide resolved
docs/source/embedding.md Outdated Show resolved Hide resolved
ipywidgets/widgets/embed.py Outdated Show resolved Hide resolved
ipywidgets/widgets/embed.py Outdated Show resolved Hide resolved
ipywidgets/widgets/embed.py Outdated Show resolved Hide resolved

if embed_url is None:
# TODO: Get widgets npm version automatically:
embed_url = u'https://unpkg.com/jupyter-js-widgets@~3.0.0-alpha.0/dist/embed.js'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this default should be defined at the top of the module so it's easy to change. Also, it should be updated now that we split the packages up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is a TODO here. Previously there were talk about making a function somewhere to automatically get the right URL. I can put it as a module global if we're not doing that.

Copy link
Member

@jasongrout jasongrout Jun 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps _version.py is the right place to put the frontend version(s). Right now, we store the widget spec version there and the protocol version. Adding another attribute there for the package versions of @jupyter-widgets/base and @jupyter-widgets/controls could be the natural place.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on this, I'd say:

embed_url_default = u'https://unpkg.com/jupyter-js-widgets@~%s-%s.%s/dist/embed.js' % (ipywidgets.__jupyter_widget_version__, ...)

Where the ... would need to include alpha.0 somehow, so I guess that also needs to go int _version.py?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: What is the current URL to use? I haven't been able to track those changes perfectly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And how would the two packages work vs an overridden URL? Would an override replace both, or just one of them? Or would we need it to be a dict/sequence now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(see #1428, and in particular, see the comment #1428 (comment))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and see #1410)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And how would the two packages work vs an overridden URL? Would an override replace both, or just one of them? Or would we need it to be a dict/sequence now?

Right now there is just the html manager package, which defines the /base and /controls versions, so there is just one include. In the future, I imagine that maybe the html manager does not include the /controls, and that would be specified separately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll wait with this until package renaming is done #1447.

ipywidgets/widgets/embed.py Outdated Show resolved Hide resolved
docs/source/embedding.md Outdated Show resolved Hide resolved
vidartf added 2 commits June 22, 2017 17:13
Adds functions for generating embeddable HTML/javascript from the python
API.
A change was left behind in the editor
@jasongrout
Copy link
Member

@maartenbreddels - just to keep the license situation simpler, and since you are also a Jupyter widgets contributor - I notice that we quote your entire MIT license in one of the files for some code of yours that we are using. Would it be possible for you to consider the code we are using from ipyvolume as a separate contribution to Jupyter Widgets under the jupyter bsd license, as a Jupyter contributor, and thus just consider it under standard Jupyter contributors copyright?

(It's fine either way, but I'm just trying to simplify the license situation where convenient...)

@maartenbreddels
Copy link
Member

Yes, I agree, consider all code in this PR as a new contribution or consider the code to be relicensed under bsd.

jupyter-widgets#1387 (comment)
"consider all code in this PR as a new contribution or consider the code
to be relicensed under bsd."
@maartenbreddels
Copy link
Member

I'm not sure if there are pieces of the code that are very similar or exact copies of my code, if that is the case, maybe it's better to explicitly put this in the header (# Part of the code is based on ipyvolume, but relicensed by the author (Maarten Breddels) under BSD).

@jasongrout
Copy link
Member

I'm not sure if there are pieces of the code that are very similar or exact copies of my code, if that is the case, maybe it's better to explicitly put this in the header (# Part of the code is based on ipyvolume, but relicensed by the author (Maarten Breddels) under BSD).

I think it's also important to consider it copyright by Project Jupyter contributors (as stated at the top of the file) - otherwise the attribution requirements require us to cite your BSD license in full. Personally, I think all we need is your permission as documented here to just use the code as-is in these commits. But of course, happy to do as you wish.

@maartenbreddels
Copy link
Member

I'm fine with that, just that if someone says 'hey, this code is copied!' they don't need to worry.

@vidartf
Copy link
Member Author

vidartf commented Jun 23, 2017

Is the current notice OK? As far as I can tell that is the only remaining issue here + the CDN url, and I'd love to get this to purple soon :)

@maartenbreddels
Copy link
Member

I'm fine with it 👍

@jasongrout
Copy link
Member

jasongrout commented Jun 23, 2017

@maartenbreddels - I tweaked it to say:

+# Parts of this code is from IPyVolume (24.05.2017), used here under
+# this copyright and license with permission from the author
+# (see #1387)

to make it clearer that the copyright and the license matches the rest of the file. Is that all right with you?

@vidartf
Copy link
Member Author

vidartf commented Jun 29, 2017

I've just updated the embed CDN url to the same as what the JS code uses (https://unpkg.com/@jupyter-widgets/html-manager@*/dist/index.js), with the same TODO pointing out that this can be made more robust. With this, I think all outstanding issues commented above should be addressed.

@jasongrout
Copy link
Member

Thanks!

@jasongrout jasongrout merged commit 6a28bdf into jupyter-widgets:master Jun 29, 2017
@vidartf vidartf deleted the embed branch June 30, 2017 10:00
@github-actions github-actions bot added the resolved-locked Closed issues are locked after 30 days inactivity. Please open a new issue for related discussion. label Feb 13, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
resolved-locked Closed issues are locked after 30 days inactivity. Please open a new issue for related discussion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

build static-export from Python
4 participants