-
-
Notifications
You must be signed in to change notification settings - Fork 551
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
[WIP] Surface bookstore #4144
[WIP] Surface bookstore #4144
Conversation
this assumes that nteract/bookstore#44 has been merged and won't work without it
- traitlets need to be configurable to be dynamically set - we need to pass around the global config object in order to have access to bookstore config further down the line - for some reason if we _only_ pass around the global config object we may not be able to get access to default values when we need them (because they are still being interpreted as LazyConfigValue objects https://github.com/ipython/traitlets/blob/master/traitlets/config/loader.py#L65 rather than full configured traitlets) - changing add_handlers signature since we are passing the global config object inside web_app.settings["config"]
@takluyver @Carreau @minrk @willingc do you have any insight why trying to access |
That is so that it can allow things like |
|
||
FILE_LOADER = FileSystemLoader(PACKAGE_DIR) | ||
|
||
class NAppHandler(IPythonHandler): | ||
"""Render the nteract view""" | ||
def initialize(self, config, page): | ||
self.nteract_config = config | ||
self.nbconfig = config | ||
self.page = page |
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'm a little bit confused about the necessity of this because we can tell if bookstore is enabled by looking at self.config.NotebookApp.nbserver_extensions["bookstore"]
. This can be set in an init for NAppHandler
like this:
def initialize(self, *args, **kwargs):
self.bookstore_enabled = self.config.NotebookApp.nbserver_extensions[
"bookstore"
]
This does mean we'd want the bookstore module to be in charge of its own validation.
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.
self.config
isn't the same object once we're inside the object iself. Also, we're currently using the initialize
hook rather than overwriting the __init__
(which I think is actually the more idiomatic way to do it in tornado).
So what you're suggesting is that we don't actually do any validation in the nteract_on_jupyter extension, but that we only check to see if the serverextension is enabled?
This does mean we'd want the bookstore module to be in charge of its own validation.
I was thinking that it would be in charge of defining it's own validation but allow others to actually perform some of the validation. My thinking was that, if it is going to have "bookstore
-aware" clients that are going to alter their UI on the basis of its functionality being available, it should provide a way for those clients to validate that the functionality is actually available in a manner that it defines. Adding the check that you're describing above seems like a great idea but I think it should supplement whatever we're providing in terms of bookstore self validation tooling.
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.
Whoops! You're so right and bookstore
needs to switch to using initialize
too for its handlers. 😬
@@ -21,7 +21,8 @@ | |||
"publicUrl": "{{ public_url }}", | |||
"contentsPath": "{{ contents_path }}", | |||
"assetUrl": "{{ asset_url }}", | |||
"page": "{{ page }}" | |||
"page": "{{ page }}", | |||
"bookstoreEnabled": "{{bookstore_enabled}}" |
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 understand that that is why it is there, why would it still not be reified when I attempted to directly access the value inside a handler (via |
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.
The bookstore specific code looks fine to me. Reading through this code independent of M's changes that we have some code documentation work to do. There are too many app, nb, napp, config words that can have multiple meanings. I'm happy to go back through the Python here, document it, and see if I understand what is happening.
I'm a bit unclear as to what is generic extension handling vs bookstore extension handling and if the config changes breaks anything already deployed. Or in other words, what extensions exist beyond bookstore?
@@ -5,27 +5,22 @@ class NteractConfig(Configurable): | |||
"""The nteract application configuration object | |||
""" | |||
name = Unicode('nteract', | |||
help='The name of nteract, which is configurable for some reason') | |||
help='The name of nteract, configurable so it can be set dynamically').tag(config=True) |
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.
help='The application name. Default: 'nteract'. Configurable though most will keep the default')
config = NteractConfig(parent=nbapp) | ||
|
||
config = nbapp.config | ||
|
||
# original |
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.
Let's remove line 30
LazyConfigValue objects never get replaced with their resolved value on the config object because they can't know their value until they are loaded by the instance to be configured (and can resolve differently e.g. for different subclasses with different defaults). Essentially, the values on Config objects should generally not be read directly, only the attributes on instances that they configure. The only reasons to read from Config object directly should be edge cases and deprecations. So instead of: url = config.NteractConfig.asset_url you would always have somewhere: nteract_config = NteractConfig(config=config) # or (parent=self)
...
asset_url = nteract_config.asset_url Instantiating the class is the resolution step, which evaluates all the lazy values, dynamic defaults, etc. Additionally, it ensures that all values are defined, which is never true for the config itself. |
That makes sense and is along the lines of what I was thinking, but I wanted to make sure that that was the pattern we should be aiming at. Just to codify this:
The open question is what to do in cases like nteract_config.version = (nteract_config.version or data['version']) where we want to set a value conditionally based on the current value of the traitlet. Is the solution there to resolve it and then check the conditional? Are there likely to be any issues stemming from that? |
Also, already discussed this with @rgbkrk, but we won't be able to use self.bookstore_enabled = self.config.NotebookApp.nbserver_extensions[
"bookstore"
] due to a bug in the current notebook.config_manager.BaseJSONConfigManager and how it deals with the order of loading If you use E.g., when I have bookstore auto-enabled and print out the config available in the
and if I explicitly enable it I get
I'll need to trace down where the bug is coming from before moving forward. In the meantime, if we want this check (which seems like a good idea), another solution is to remove auto-enabling from the bookstore extension. |
Yup, that sounds right
Yeah, that's sensible, or make it a dynamic default, e.g. @default('version')
def _load_default_version(self):
package_file = os.path.join(self.assets_dir, 'package.json')
with open(package_file) as f:
data = json.load(fid)
return data['version'] which will work if you can be sure that self.assets_dir is correctly defined before .version is requested. But doing it the way you've defined is AOK as well.
This is another case of interrogating the config object directly, which I would do my best to avoid. Ideally, there will be exactly zero references to |
We'd have to do this in the handler after all extensions are loaded I assume. |
Since all extension loading is done after finishing populating |
Here's a question, though: instead of asking the configuration or the parent application if yourself is enabled as an extension, maybe set a private flag when your extension is loaded that you can check? Then you aren't concerned with any state implementation details of the notebook server; you track yourself whether your extension is loaded: _extension_loaded = False
def _load_jupyter_server_extension(self, app):
global _extension_loaded
_extension_loaded = True
... |
@@ -21,7 +21,8 @@ | |||
"publicUrl": "{{ public_url }}", | |||
"contentsPath": "{{ contents_path }}", | |||
"assetUrl": "{{ asset_url }}", | |||
"page": "{{ page }}" | |||
"page": "{{ page }}", | |||
"bookstoreEnabled": "{{bookstore_enabled}}" |
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.
from talking with @stormpython we realized we should only surface this if it's validated and that bookstore's different pieces of functionality will require different validation
accordingly, this should become an object and not just a boolean.
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.
proposed new name "bookstoreValidated"
The purpose of this PR is to enable us to know on the front-end whether or not bookstore has been enabled.
This PR needs nteract/bookstore#44 in order to be able to report
bookstoreEnabled: "True"
.However, a lot of decisions and refactoring needed to happen to enable this.
Why did this require a refactor?
It made the most sense (IMO) to put the check as locally as possible to the handler itself, because that is where much of the rest of the information being sent to the "jupyter-config-data" script.
In order for us to do that, we needed to have access to bookstore settings. Bookstore settings are defined via the BookstoreSettings Configurable object and set via traitlet configuration assignment.
Previously, we had been instantiating a
NteractConfig
object inside theextension
module using the nbapp's config. We were assigning values to that object (post instantiation) and were passing it through to ouradd_handlers
function that was defined in thehandlers
module.After some initial exploration, I discovered that we could not simply pass the
nbapp
's config object to theadd_handlers
function, because the config values were only being assigned to the localNteractConfig
object and were not propagated back to thenbapp
's config object. Values were also being assigned inside theadd_handlers
function (these also would not be propagated back to thenbapp
's config object.Unfortunately, simply passing the global config object through was insufficient, because for traitlet values that had not been directly assigned and were not explicitly configured, when accessed on the global config object they had no value and were still treated as instances of LazyConfigValue (see https://github.com/ipython/traitlets/blob/d07d70d81e82f655ac36841415721d5246876538/traitlets/config/loader.py#L65-L73)
Solution:
In order to get access to the global config object later, we add it to the
webapp.settings
and then explicitly instantiate the NteractConfig and the BookstoreSettings inside the actual handler code.