-
Notifications
You must be signed in to change notification settings - Fork 945
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
Conversation
I just added that description style model. Happy to help if I can. |
Ah, then I'm guessing it is simply the fact that it is not available in the version on the CDN yet. |
Right, it isn't in the cdn yet
…On Wed, May 24, 2017, 11:17 Vidar Tonaas Fauske ***@***.***> wrote:
Ah, then I'm guessing it is simply the fact that it is not available in
the version on the CDN yet.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#1387 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AALwZp2F77MSmJewMDmEPML4UsYBlYiiks5r9En8gaJpZM4NlNm4>
.
|
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 |
@maartenbreddels : I figured if someone wanted more custom HTML, they could simply use the snippet code, and then wrap it themselves ( Having the url overridable sounds like a good idea. |
Regarding the javascript embed parser: 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? |
Order doesn't matter afaik. |
@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: 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 |
Are all three views added? I would think that just one view should be added, i.e., the top-level widget that is displayed. |
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? |
@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! |
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! |
@vidartf just want to make you aware of what's happening in #1394 . It would make sense this PR would also include a |
@maartenbreddels Yes, there is a |
@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? |
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). |
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. |
ipywidgets/widgets/embed.py
Outdated
if include_all: | ||
state = Widget.get_manager_state(drop_defaults=drop_defaults, widgets=None)['state'] | ||
else: | ||
state = dependency_state(views, drop_defaults) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 argumentstate=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?
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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! :)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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.
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. |
There was a problem hiding this 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
?
ipywidgets/widgets/embed.py
Outdated
|
||
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' |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(and see #1410)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Adds functions for generating embeddable HTML/javascript from the python API.
Note: This should be a pure move, except changes to relative imports.
recommonmark rst eval is not enabled, so fix the formatting
@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...) |
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."
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. |
I'm fine with that, just that if someone says 'hey, this code is copied!' they don't need to worry. |
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 :) |
I'm fine with it 👍 |
@maartenbreddels - I tweaked it to say:
to make it clearer that the copyright and the license matches the rest of the file. Is that all right with you? |
Also copy over the TODO
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. |
Thanks! |
Adds functions to the Python side API for generating embeddable javascript/HTML representation of the widgets. Based on code from ipyvolume.
Resolves #1241.
TODOs
Could not create model: Model name: DescriptionStyleModel
that appears regularly for full saves.