From b20b820261bfa01425cc0f410ce49f14e93140ce Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Fri, 10 Nov 2023 22:01:32 -0500 Subject: [PATCH 1/4] Update widget to use ES6 class syntax. --- frontend/lib/widget.js | 105 ++++++++++++++++++++--------------------- 1 file changed, 52 insertions(+), 53 deletions(-) diff --git a/frontend/lib/widget.js b/frontend/lib/widget.js index 44377918..d10b27f5 100644 --- a/frontend/lib/widget.js +++ b/frontend/lib/widget.js @@ -67,21 +67,24 @@ var version = require('./index').version; // Which is a hairy busines, because not only does our one widget potentially // have multiple views, but there are also potentially multiple active widgets, // and we'll see all of their messages. -var WWTModel = widgets.DOMWidgetModel.extend({ - defaults: _.extend(widgets.DOMWidgetModel.prototype.defaults(), { - _model_name: 'WWTModel', - _model_module: 'pywwt', - _model_module_version: version, - - _view_name: 'WWTView', - _view_module: 'pywwt', - _view_module_version: version, - - _appUrl: '' - }), +class WWTModel extends widgets.DOMWidgetModel { + defaults() { + return { + ...super.defaults(), + _model_name: 'WWTModel', + _model_module: 'pywwt', + _model_module_version: version, + + _view_name: 'WWTView', + _view_module: 'pywwt', + _view_module_version: version, + + _appUrl: '' + } + } - initialize: function () { - WWTModel.__super__.initialize.apply(this, arguments); + initialize() { + widgets.DOMWidgetModel.prototype.initialize.apply(this, arguments); // NOTE: we deliberately call the following twice to make sure that it // is properly set, due to a caching bug in some versions of JupyterLab. @@ -112,12 +115,12 @@ var WWTModel = widgets.DOMWidgetModel.extend({ function (event) { self.processDomWindowMessage(event); }, false ); - }, + } // The kernel can generate partial URLs, but doesn't (and can't) know the // full URL where data are ultimately exposed. So in various places we need // to edit URLs emerging from the client to make them complete. - canonicalizeUrl: function (url) { + canonicalizeUrl(url) { // Sketchy heuristic to deal with the Jupyter "base URL", which still // isn't an absolute URL. It's a URL path used by multi-user Jupyter // servers and the like. The Python kernel code can determine the base @@ -132,19 +135,19 @@ var WWTModel = widgets.DOMWidgetModel.extend({ } return new URL(url, location.toString()).toString(); - }, + } // Get a unique ID and sequence number for distinguishing views. Note that // while each model might have multiple views, there might also be multiple // widget models too, and we have to distinguish them all. - mintViewIds: function() { + mintViewIds() { var seq = this._nextViewSeqNumber; this._nextViewSeqNumber++; return [this.model_id + "v" + seq, seq]; - }, + } // Called by a widget view when the "liveness" state of its app changes. - onViewStatusChange: function(view, alive) { + onViewStatusChange(view, alive) { if (alive) { // Should this view become the current view? // @@ -187,14 +190,14 @@ var WWTModel = widgets.DOMWidgetModel.extend({ } } } - }, + } // Relay a message from the kernel to the active view. In order to keep // things tractable, we only route messages to the "current" view. For // instance, if the client were to issue a data-request message and we // routed it to multiple views, we'd get multiple responses, with no // sensible way to know which to prefer. - processIpyWidgetsMessage: function (msg) { + processIpyWidgetsMessage(msg) { if (this._currentView === null) { // We could queue up messages here. The kernel "shouldn't" send us // any messages until a view is ready, but it's always possible that @@ -224,7 +227,7 @@ var WWTModel = widgets.DOMWidgetModel.extend({ } this._currentView.relayIpyWidgetsMessage(msg); - }, + } // Process messages from the WWT apps, potentially relaying them to the // kernel. @@ -234,7 +237,7 @@ var WWTModel = widgets.DOMWidgetModel.extend({ // // The message is relayed to the kernel using ipywidgets "custom" messages, // which is basically trivial once we've dealt with the above. - processDomWindowMessage: function (event) { + processDomWindowMessage(event) { var payload = event.data; if (event.origin !== this._appOrigin) @@ -270,7 +273,7 @@ var WWTModel = widgets.DOMWidgetModel.extend({ payload['_pywwtExpedite'] = true; this.send(payload); } -}); +} // The pywwt ipywidget view implementation. // @@ -282,8 +285,8 @@ var WWTModel = widgets.DOMWidgetModel.extend({ // destroy the element. However, re-adding an iframe to the DOM causes it to // reload, so hiding and re-showing a WWT view causes its internal state to be // reset :-( -var WWTView = widgets.DOMWidgetView.extend({ - render: function () { +class WWTView extends widgets.DOMWidgetView { + render() { this._appUrl = this.model.canonicalizeUrl(this.model.get('_appUrl')); this._appOrigin = new URL(this._appUrl).origin; @@ -324,9 +327,9 @@ var WWTView = widgets.DOMWidgetView.extend({ ); setInterval(function () { self.checkApp(); }, 1000); - }, + } - checkApp: function() { + checkApp() { // Send our next ping ... var window = this.tryGetWindow(); @@ -346,13 +349,13 @@ var WWTView = widgets.DOMWidgetView.extend({ this._alive = alive; this.model.onViewStatusChange(this, alive); } - }, + } // Process a message sent to the browser window. This function's only job is // to look for responses to our pings. It will be called for messages from // all views of all widgets, though, so it needs to be careful about which // messages to process. - processDomWindowMessage: function (event) { + processDomWindowMessage(event) { var payload = event.data; if (event.origin !== this._appOrigin) @@ -365,12 +368,12 @@ var WWTView = widgets.DOMWidgetView.extend({ this._lastPongTimestamp = ts; } } - }, + } // Called by the model when there's a message from the kernel that should go // to this view. The model "shouldn't" give us any messages if/when our // window is nonfunctional, but the window might always die underneath us. - relayIpyWidgetsMessage: function (msg) { + relayIpyWidgetsMessage(msg) { var window = this.tryGetWindow(); if (!window) { // TODO? Tell the model that we failed? @@ -378,38 +381,34 @@ var WWTView = widgets.DOMWidgetView.extend({ } window.postMessage(msg, this._appUrl); - }, + } // Note: processPhosphorMessage is needed for Jupyter Lab <2 and // processLuminoMessage is needed for Jupyter Lab 2.0+ - processPhosphorMessage: function (msg) { + // See https://ipywidgets.readthedocs.io/en/latest/migration_guides.html#phosphor-lumino + _processLuminoMessage(msg, _super) { // We listen for phosphor resize events so that when Jupyter Lab is // used, we adjust the canvas size to the tab/panel in Jupyter Lab. // See relayout for more details. - WWTView.__super__.processPhosphorMessage.apply(this, arguments); + _super.call(this, msg); switch (msg.type) { case 'resize': case 'after-show': this.relayout(); break; } - }, + } - processLuminoMessage: function (msg) { - // We listen for lumino resize events so that when Jupyter Lab is - // used, we adjust the canvas size to the tab/panel in Jupyter Lab. - // See relayout for more details. - WWTView.__super__.processLuminoMessage.apply(this, arguments); - switch (msg.type) { - case 'resize': - case 'after-show': - this.relayout(); - break; - } - }, + processPhosphorMessage(msg) { + this._processLuminoMessage(msg, super.processPhosphorMessage); + } - relayout: function () { + processLuminoMessage(msg) { + this._processLuminoMessage(msg, super.processLuminoMessage); + } + + relayout() { // Only do resizing if we are not in the notebook context but in a // split panel context. We find this out by checking if one of the // parents of the current element has the jp-MainAreaWidget class -- @@ -459,13 +458,13 @@ var WWTView = widgets.DOMWidgetView.extend({ // need to find a better solution in the long term. iframe.width = width - 10; iframe.height = height - 10; - }, + } // Get the WWT window, if it is actually fully initialized. Note that in // JupyterLab if this widget view is hidden and then re-shown, the iframe // will reload, and the contentWindow will acquire a new value. So we can't // cache too aggressively. - tryGetWindow: function () { + tryGetWindow() { var iframe = this.el.getElementsByTagName('iframe')[0]; if (!iframe) return null; @@ -476,7 +475,7 @@ var WWTView = widgets.DOMWidgetView.extend({ return window; } -}); +} module.exports = { WWTModel: WWTModel, From 6830896507f96876743bf613ffdf5fe4894accbf Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Tue, 28 Nov 2023 09:41:30 -0500 Subject: [PATCH 2/4] ci: temporarily disable Zenodo so we can get a release out --- ci/azure-deployment.yml | 62 +++++++++++++++++++++-------------------- ci/azure-sdist.yml | 14 ++++++---- 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/ci/azure-deployment.yml b/ci/azure-deployment.yml index 23725ba3..9bf955a7 100644 --- a/ci/azure-deployment.yml +++ b/ci/azure-deployment.yml @@ -111,33 +111,35 @@ jobs: - bash: shred ~/.npmrc displayName: Clean up credentials - - job: zenodo_publish - pool: - vmImage: ubuntu-latest - variables: - - group: Deployment Credentials - - steps: - - template: azure-job-setup.yml - parameters: - setupCranko: true - - - bash: | - set -xeuo pipefail - - if cranko show if-released --exit-code pypa:pywwt ; then - cranko zenodo upload-artifacts --metadata=ci/zenodo.json5 $BASH_WORKSPACE/sdist/*.tar.gz - fi - displayName: Upload source tarball - env: - ZENODO_TOKEN: $(ZENODO_TOKEN) - - - bash: | - set -xeuo pipefail - - if cranko show if-released --exit-code pypa:pywwt ; then - cranko zenodo publish --metadata=ci/zenodo.json5 - fi - displayName: Publish to Zenodo - env: - ZENODO_TOKEN: $(ZENODO_TOKEN) + # 2023 Nov: temporarily disabling Zenodo; they have just updated their API and broken + # everything, and we want to ge a release out. + #- job: zenodo_publish + # pool: + # vmImage: ubuntu-latest + # variables: + # - group: Deployment Credentials + # + # steps: + # - template: azure-job-setup.yml + # parameters: + # setupCranko: true + # + # - bash: | + # set -xeuo pipefail + # + # if cranko show if-released --exit-code pypa:pywwt ; then + # cranko zenodo upload-artifacts --metadata=ci/zenodo.json5 $BASH_WORKSPACE/sdist/*.tar.gz + # fi + # displayName: Upload source tarball + # env: + # ZENODO_TOKEN: $(ZENODO_TOKEN) + # + # - bash: | + # set -xeuo pipefail + # + # if cranko show if-released --exit-code pypa:pywwt ; then + # cranko zenodo publish --metadata=ci/zenodo.json5 + # fi + # displayName: Publish to Zenodo + # env: + # ZENODO_TOKEN: $(ZENODO_TOKEN) diff --git a/ci/azure-sdist.yml b/ci/azure-sdist.yml index 859ebdc6..ea790a7a 100644 --- a/ci/azure-sdist.yml +++ b/ci/azure-sdist.yml @@ -29,12 +29,14 @@ jobs: - bash: cranko release-workflow apply-versions displayName: Apply Cranko versions - - bash: | - cranko zenodo preregister --metadata=ci/zenodo.json5 pypa:pywwt pywwt/_version.py CHANGELOG.md - displayName: "Preregister Zenodo DOI" - ${{ if and(eq(variables['Build.SourceBranchName'], 'rc'), ne(variables['build.reason'], 'PullRequest')) }}: - env: - ZENODO_TOKEN: $(ZENODO_TOKEN) + # 2023 Nov: temporarily disabling Zenodo; they have just updated their API and broken + # everything, and we want to ge a release out. + # - bash: | + # cranko zenodo preregister --metadata=ci/zenodo.json5 pypa:pywwt pywwt/_version.py CHANGELOG.md + # displayName: "Preregister Zenodo DOI" + # ${{ if and(eq(variables['Build.SourceBranchName'], 'rc'), ne(variables['build.reason'], 'PullRequest')) }}: + # env: + # ZENODO_TOKEN: $(ZENODO_TOKEN) - bash: | set -xeuo pipefail From f10468c1593f8e85cb806813eb23123467c227d7 Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Tue, 28 Nov 2023 11:10:23 -0500 Subject: [PATCH 3/4] pywwt/core.py: reformat with "black" --- pywwt/core.py | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/pywwt/core.py b/pywwt/core.py index cc29fd7e..87b2fc60 100644 --- a/pywwt/core.py +++ b/pywwt/core.py @@ -610,8 +610,9 @@ def instruments(self): ).tag(wwt="altAzGridColor", wwt_reset=True) alt_az_text = Bool( - False, help='Whether to show labels for the altitude-azimuth grid\'s text ' '(`bool`)' - ).tag(wwt='showAltAzGridText', wwt_reset=True) + False, + help="Whether to show labels for the altitude-azimuth grid's text " "(`bool`)", + ).tag(wwt="showAltAzGridText", wwt_reset=True) background = Unicode( "Hydrogen Alpha Full Sky Map", @@ -651,13 +652,15 @@ def instruments(self): ).tag(wwt="showConstellationSelection", wwt_reset=True) constellation_pictures = Bool( - False, help='Whether to show pictures of the constellations\' ' - 'mythological representations ' '(`bool`)' - ).tag(wwt='showConstellationPictures', wwt_reset=True) + False, + help="Whether to show pictures of the constellations' " + "mythological representations " + "(`bool`)", + ).tag(wwt="showConstellationPictures", wwt_reset=True) constellation_labels = Bool( - False, help='Whether to show labels for constellations ' '(`bool`)' - ).tag(wwt='showConstellationLabels', wwt_reset=True) + False, help="Whether to show labels for constellations " "(`bool`)" + ).tag(wwt="showConstellationLabels", wwt_reset=True) crosshairs = Bool( False, help="Whether to show crosshairs at the center of " "the field (`bool`)" @@ -711,8 +714,8 @@ def instruments(self): ).tag(wwt="galacticGridColor", wwt_reset=True) galactic_text = Bool( - False, help='Whether to show labels for the galactic grid\'s text ' '(`bool`)' - ).tag(wwt='showGalacticGridText', wwt_reset=True) + False, help="Whether to show labels for the galactic grid's text " "(`bool`)" + ).tag(wwt="showGalacticGridText", wwt_reset=True) grid = Bool(False, help="Whether to show the equatorial grid " "(`bool`)").tag( wwt="showGrid", wwt_reset=True @@ -871,11 +874,13 @@ def center_on_coordinates(self, coord, fov=60 * u.deg, roll=None, instant=True): desired location. """ coord_icrs = coord.icrs - msg = dict(event="center_on_coordinates", - ra=coord_icrs.ra.deg, - dec=coord_icrs.dec.deg, - fov=fov.to(u.deg).value, - instant=instant) + msg = dict( + event="center_on_coordinates", + ra=coord_icrs.ra.deg, + dec=coord_icrs.dec.deg, + fov=fov.to(u.deg).value, + instant=instant, + ) if roll is not None: msg["roll"] = roll.to(u.deg).value self._send_msg(**msg) @@ -1069,11 +1074,11 @@ def load_image_collection(self, url, recursive=False, remote_only=False): nest_asyncio.apply() loop = asyncio.get_event_loop() - loop.run_until_complete(self._send_into_future( - event="load_image_collection", - url=url, - loadChildFolders=recursive - )) + loop.run_until_complete( + self._send_into_future( + event="load_image_collection", url=url, loadChildFolders=recursive + ) + ) @property def available_layers(self): @@ -1341,7 +1346,7 @@ def _serialize_state(self, title, max_width, max_height): "ra": center.icrs.ra.deg, "dec": center.icrs.dec.deg, "fov": fov.to_value(u.deg), - "roll": roll.to_value(u.deg) + "roll": roll.to_value(u.deg), } state["foreground_settings"] = { From ce7e4701840ba984d8f362cc887ed7957c936b65 Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Tue, 28 Nov 2023 11:13:59 -0500 Subject: [PATCH 4/4] ci/azure-build-and-test.yml: fix up docs build for pip/Conda disagreement --- ci/azure-build-and-test.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ci/azure-build-and-test.yml b/ci/azure-build-and-test.yml index 962bb45f..777de085 100644 --- a/ci/azure-build-and-test.yml +++ b/ci/azure-build-and-test.yml @@ -213,11 +213,31 @@ jobs: parameters: setupBuild: true + # We install some extra deps here to make the docs build happy (it needs to + # import everything) while avoiding clashes between Conda and pip. - bash: | set -euo pipefail source activate-conda.sh set -x - \conda create -y -n build setuptools matplotlib pip pyopengl 'pyqt>=5.12' pyqtwebengine python=3.9 qtpy scipy shapely + \conda create -y -n build \ + astropy \ + astropy-sphinx-theme \ + ipykernel \ + jupyter_sphinx \ + matplotlib \ + nbclassic \ + numpydoc \ + pip \ + pyopengl \ + 'pyqt>=5.12' \ + pyqtwebengine \ + python=3.9 \ + qtpy \ + scipy \ + setuptools \ + shapely \ + sphinx \ + sphinx-automodapi conda activate build pip install $BASH_WORKSPACE/sdist/*.tar.gz displayName: Install from sdist @@ -230,7 +250,6 @@ jobs: source activate-conda.sh conda activate build set -x - \conda install -y astropy astropy-sphinx-theme ipykernel jupyter_sphinx nbclassic numpydoc sphinx sphinx-automodapi cd docs make html displayName: Build docs