Open in Binder Chrome Extension

Two weeks ago I was pleased to announce the release of the Open-with-Binder for Firefox extension.

After asking on twitter if people were interested in the same for Chrome (29 Yes, 67 No, 3 Other) and pondering whether or not to pay the Chrome Developer Fee for the Chrome App store, I decided to take my chance and try to publish it last week.

I almost just had to use Mozilla WebExt Shim for Chrome, downgrade a few artwork from SVG to PNG (like really??) and upload all by hand, like really again ?

The Chrome Store has way more fields and it is quite complicated – compared to the Mozilla Addons website at least – It is sometime confusing whether fields are optional or not, or if they are per addons on per developer ?

It does though allow you to upload more art that will be show in a store which that looks nicer.

Still I had to pay to go through a really ugly crappy website and had to pay for it to publish a free extension. So Mozilla you win this.

Please rate the extension, or it may not appear in search results for others AFAICT:

open with Binder install Open with Binder for chrome

It works identically to the Firefox one, you get a button on the toolbar and click on it when visiting GitHub.

Enjoy.

Open in Binder Browser Extension

Today I am please to announce the release of a first project I've been working on for a bout a week: A Firefox extension to open the GitHub repository you are visiting using MyBinder.org.

If you are in a hurry, just head there to Install version 0.1.0 for Firefox. If you like to know more read on.

Binder Logo

Back to Firefox.

I've been using Chrome for a couple of years now, but heard a lot of good stuff about Rust and all the good stuff it has done or Firefox. Ok that's a bit of marketing but it got me to retry Firefox (Nightly please), and except for my password manager which took some week to update to the new Firefox API, I rapidly barely used Chrome.

Firefox Nightly Logo

MyBinder.org

I'm also spending more and more time working with the JupyterHub team on Binder, and see more and more developer adding binder badges to their repository. Mid of last week I though:

You know what's not optimal? It's painful to browse repositories that don't have the binder badge on MyBinder.org, also sometime you have to find the badge which is at the bottom of the readme.

You know what would be great to fix that ? A button in the toolbar doing the work for me.

Writing the extension

As I know Mozilla (which has a not so great new design BTW, but personal opinion) cares about making standard and things simple for their users, I though I would have a look at the new WebExtension.

And 7 days later, after a couple of 30 minutes break, I present to you a staggering 27 lines (including 7 line business logic) extension that does that:

(function() {
  function handleClick(){
    browser.tabs.query({active: true, currentWindow: true})
    .then((tabs) => {return tabs[0]})
    .then((tab) => {
      let url = new URL(tab.url);
      if (url.hostname != 'github.com'){
       console.warn('Open in binder only works on GitHub repositories for now.');
       return;
      };
      let parts = url.pathname.split('/');
      if (parts.length < 3){
        console.warn('While you are on GitHub, You do not appear to be in a github repository. Aborting.');
        return;
      }
      let my_binder_url = 'https://mybinder.org/v2/gh/'+parts[1] +'/'+parts[2] +'/master';
      console.info('Opening ' + url + 'using mybinder.org... enjoy !')
      browser.tabs.create({'url':my_binder_url});
    })

  }
  console.info('(Re) loading open-in-binder extension.');
  browser.browserAction.onClicked.addListener(handleClick);

  console.info('❤️ If you are reading this then you know about binder and javascript. ❤️');
  console.info('❤️ So you\'re skilled enough to contribute ! We\'re waiting for you on https://github.com/jupyterhub/ ❤️');
})()

You can find the original source here

Firefox Dev Logo

The hardest part was finding the API and learning how to package and set the icons correctly. There are still plenty of missing features and really low hanging fruits, even if you have never written an extension before (hey it's my first and I averaged 1-useful line/day writing it...).

General Feeling

Remember that I'm new to that and started a week ago.

The Mozilla docs are good but highly varying in quality, it feels (and is) a wiki. More opinionated tutorials might have been less confusing. A lot of statements are correct but not quite, and leaving the choice too users is just confusing. For example : you can use SVG or PNG icons, which I did, but then some area don't like SVG (addons.mozilla.org), and the WebExtensions should work on Chrome, but Chrome requires PNG. Telling me that I could use SVG was not useful.

The review of addons is blazingly fast (7min from first submissions to Human approved). Apple could learn from that if what I've heard here and there is correct..

The submission process has way to many manual steps, I'm ok for first submission, but updates, really ? I want to be able to fill-in all the information ahead of time (or generate them) and then have a cli to submit things. I hate filling forms online.

The first submission even if marked Beta will not be considered beta. So basically I published a 0.1.0beta1, then 0.1.0beta2 which did not trigger automatic update because the beta1 was not considered beta. Super confusing. I could "force" to see the beta3 page but with a warning that beta3 was an older version than beta1 ? What ?

There is still this feeling that this last 1% of polishing the process has not been done (That's usually where Apple is know to shine). For example your store icon will be resized to 64x64 (px) and display in a 64x64 (px) square but I have a retina screen ! So even if I submitted a 128x128 now my icon looks blurry ! WTF !

You can contribute

As I said earlier there is a lot of low hanging fruits ! I went through the process of figuring things out, so that you can contribute easily:

  • detect if not on /master/ and craft corresponding binder URL
  • Switch Icons to PNGs
  • test/package for Chrome
  • Add options for other binders than MyBinder.org
  • Add Screenshots and descriptions to the Addon Store.

So see you there !

JupyterCon - Display Protocol

This is an early preview of what I am going to talk about at Jupyter Con

Leveraging the Jupyter and IPython display protocol

This is a small essay to show how one can make a better use of the display protocol. All you will see in this blog post has been available for a couple of years but noone really built on top of this.

It is usually know that the IPython rich display mechanism allow libraries authors to define rich representation for their objects. You may have seen it in SymPy, which make extensive use of the latex representation, and Pandas which dataframes have nice HTML view.

What I'm going to show below, is that one is not limited to these – you can alter the representation of any existing object without modifying its source – and that this can be used to alter the view of containers, with the example of lists, to make things easy to read.

Modifying objects reprs

This section is just a reminder of how one can change define representation for object which source code is under your control. When defining a class, the code author needs to define a number of methods which should return the (data, metadata) pair for a given object mimetype. If no metadata is necesary, these can be ommited. For some common representations short methods name ara availables. These methond can be recognized as they all follow the following pattern _repr_*_(self). That is to say, an underscore, followed by repr followed by an underscore. The star * need to be replaced by a lowercase identifier often refering to a short human redable description of the format (e.g.: png , html, pretty, ...), ad finish by a single underscore. We note that unlike the python __repr__ (pronouced "Dunder rep-er" which starts and ends wid two underscore, the "Rich reprs" or "Reprs-stars" start and end with a single underscore.

Here is the class definition of a simple object that implements three of the rich representation methods:

  • "text/html" via the _repr_html_ method
  • "text/latex" via the _repr_latex_ method
  • "text/markdown" via the _repr_markdown method

None of these methonds return a tuple, thus IPython will infer that there is no metadata associated.

The "text/plain" mimetype representation is provided by the classical Python's __repr__(self).

In [1]:
class MultiMime:
    
    def __repr__(self):
        return "this is the repr"
    
    def _repr_html_(self):
        return "This <b>is</b> html"
    
    def _repr_markdown_(self):
        return "This **is** mardown"

    def _repr_latex_(self):
        return "$ Latex \otimes mimetype $"
In [2]:
MultiMime()
Out[2]:
This is html

All the mimetypes representation will be sent to the frontend (in many cases the notebook web interface), and the richer one will be picked and displayed to the the user. All representations are stored in the notebook document (on disk) and this can be choosen from when the document is later reopened – even with no kernel attached – or converted to another format.

External formatters and containers

As stated in teh introduction, you do not need to have control over an object source code to change its representation. Still it is often a more convenient process. AS an example we will build a Container for image thumbnails and see how we can use the code written for this custom container to apply it to generic Python containers like lists.

As a visual example we'll use Orly Parody books covers, in particular a small resolution of some of them so llimit the amount of data we'll be working with.

In [3]:
cd thumb
/Users/bussonniermatthias/dev/posts/thumb

let's see some of the images present in this folder:

In [4]:
names = !ls *.png
names[:20], f"{len(names) - 10} more"
Out[4]:
(['10x-big.png',
  'adulting-big.png',
  'arbitraryforecasts-big.png',
  'avoiddarkpatterns-big.png',
  'blamingthearchitecture-big.png',
  'blamingtheuser-big.png',
  'breakingthebackbutton-big.png',
  'buzzwordfirst-big.png',
  'buzzwordfirstdesign-big.png',
  'casualsexism-big.png',
  'catchingemall-big.png',
  'changinstuff-big.png',
  'chasingdesignfads-big.png',
  'choosingbasedongithubstars-big.png',
  'codingontheweekend-big.png',
  'coffeeintocode-big.png',
  'copyingandpasting-big.png',
  'crushingit-big.png',
  'deletingcode-big.png',
  'doingwhateverdanabramovsays-big.png'],
 '63 more')

in the above i've used an IPython specific syntax (!ls) ton conveniently extract all the files with a png extension (*.png) in the current working directory, and assign this to teh names variable.

That's cute, but, for images, not really usefull. We know we can display images in the Jupyter notebook when using the IPython kernel, for that we can use the Image class situated in the IPython.display submodule. We can construct such object simply by passing the filename. Image does already provide a rich representation:

In [5]:
from IPython.display import Image
In [6]:
im = Image(names[0])
im
Out[6]:

The raw data from the image file is available via the .data attribute:

In [7]:
im.data[:20]
Out[7]:
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x90'

What if we map Images to each element of a list ?

In [8]:
from random import choices
mylist = list(map(Image, set(choices(names, k=10))))
mylist
Out[8]:
[<IPython.core.display.Image object>,
 <IPython.core.display.Image object>,
 <IPython.core.display.Image object>,
 <IPython.core.display.Image object>,
 <IPython.core.display.Image object>,
 <IPython.core.display.Image object>,
 <IPython.core.display.Image object>,
 <IPython.core.display.Image object>,
 <IPython.core.display.Image object>]

Well unfortunately a list object only knows how to represent itself using text and the text representation of its elements. We'll have to build a thumbnail gallery ourself.

First let's (re)-build an HTML representation for display a single image:

In [9]:
import base64
from IPython.display import HTML
def tag_from_data(data, size='100%'):
    return (
        '''<img
             style="display:inline;
                    width:{1};
                    max-width:400px;
                    padding:10px;
                    margin-top:14px"
             src="data:image/png;base64,{0}"
           />
           ''').format(''.join(base64.encodebytes(data).decode().split('\n')), size)

We encode the data from bytes to base64 (newline separated), and strip the newlines. We format that into an Html template – with some inline style – and set the source (src to be this base64 encoded string). We can check that this display correctly by wrapping the all thing in an HTML object that provide a conveninent _repr_html_.

In [10]:
HTML(tag_from_data(im.data))
Out[10]:

Now we can create our own subclass, hich take a list of images and contruct and HTML representation for each of these, then join them together. We define and define a _repr_html_, that wrap the all in a paragraph tag, and add a comma between each image:

In [11]:
class VignetteList:
    
    
    def __init__(self, *images, size=None):
        self.images = images
        self.size = size
        
    def _repr_html_(self):
        return '<p>'+','.join(tag_from_data(im.data, self.size)  for im in self.images)+'</p>'
    
    def _repr_latex_(self):
        return '$ O^{rly}_{books} (%s\ images)$ ' % (len(self.images))
        

We also define a LaTeX Representation – that we will not use here, and look at our newly created object using previously defined list:

In [12]:
VignetteList(*mylist, size='200px')
Out[12]:

, , ,