Repository: jupyterhub/binderhub
Pull request #967 (2019-09-28 01:43:56)

[MRG] add hydroshare as a repo provider
repo2docker changes:
https://github.com/jupyter/repo2docker/pull/800


Patch:
diff --git a/binderhub/app.py b/binderhub/app.py
old mode 100644
new mode 100755
index afec261a..0aba7819
--- a/binderhub/app.py
+++ b/binderhub/app.py
@@ -34,7 +34,7 @@ from .registry import DockerRegistry
 from .main import MainHandler, ParameterizedMainHandler, LegacyRedirectHandler
 from .repoproviders import (GitHubRepoProvider, GitRepoProvider,
                             GitLabRepoProvider, GistRepoProvider,
-                            ZenodoProvider, FigshareProvider,
+                            ZenodoProvider, FigshareProvider, HydroshareProvider,
                             DataverseProvider)
 from .metrics import MetricsHandler
 
@@ -404,6 +404,7 @@ class BinderHub(Application):
             'gl': GitLabRepoProvider,
             'zenodo': ZenodoProvider,
             'figshare': FigshareProvider,
+            'hydroshare': HydroshareProvider,
             'dataverse': DataverseProvider,
         },
         config=True,
diff --git a/binderhub/event-schemas/launch.json b/binderhub/event-schemas/launch.json
old mode 100644
new mode 100755
index 8e019748..662d0368
--- a/binderhub/event-schemas/launch.json
+++ b/binderhub/event-schemas/launch.json
@@ -13,6 +13,7 @@
                 "Git",
                 "Zenodo",
                 "Figshare",
+                "Hydroshare",
                 "Dataverse"
             ],
             "description": "Provider for the repository being launched"
diff --git a/binderhub/main.py b/binderhub/main.py
old mode 100644
new mode 100755
index dac63377..32963423
--- a/binderhub/main.py
+++ b/binderhub/main.py
@@ -17,6 +17,7 @@ SPEC_NAMES = {
     "git": "Git repo",
     "zenodo": "Zenodo",
     "figshare": "Figshare",
+    "hydroshare": "Hydroshare",
     "dataverse": "Dataverse",
 }
 
diff --git a/binderhub/repoproviders.py b/binderhub/repoproviders.py
old mode 100644
new mode 100755
index 16067652..d84e0e59
--- a/binderhub/repoproviders.py
+++ b/binderhub/repoproviders.py
@@ -7,7 +7,7 @@ control services and providers.
 .. note:: When adding a new repo provider, add it to the allowed values for
           repo providers in event-schemas/launch.json.
 """
-from datetime import timedelta
+from datetime import timedelta, datetime, timezone
 import json
 import os
 import time
@@ -351,6 +351,58 @@ class DataverseProvider(RepoProvider):
         return "dataverse-" + escapism.escape(self.identifier, escape_char="-").lower()
 
 
+class HydroshareProvider(RepoProvider):
+    """Provide contents of a Hydroshare resource
+    Users must provide a spec consisting of the Hydroshare resource id.
+    """
+    name = Unicode("Hydroshare")
+    url_regex = re.compile(r".*([0-9a-f]{32}).*")
+
+    def _parse_resource_id(self, spec):
+        match = self.url_regex.match(spec)
+        if not match:
+            raise ValueError("The specified Hydroshare resource id was not recognized.")
+        resource_id = match.groups()[0]
+        return resource_id
+
+    @gen.coroutine
+    def get_resolved_ref(self):
+        client = AsyncHTTPClient()
+        self.resource_id = self._parse_resource_id(self.spec)
+        req = HTTPRequest("https://www.hydroshare.org/hsapi/resource/{}/scimeta/elements".format(self.resource_id),
+                          user_agent="BinderHub")
+        r = yield client.fetch(req)
+        def parse_date(json_body):
+            json_response = json.loads(json_body)
+            date = next(
+                item for item in json_response["dates"] if item["type"] == "modified"
+            )["start_date"]
+            # Hydroshare timestamp always returns the same timezone, so strip it
+            date = date.split(".")[0]
+            parsed_date = datetime.strptime(date, "%Y-%m-%dT%H:%M:%S")
+            epoch = parsed_date.replace(tzinfo=timezone(timedelta(0))).timestamp()
+            # truncate the timestamp
+            return str(int(epoch))
+        # date last updated is only good for the day... probably need something finer eventually
+        self.record_id = "{}.v{}".format(self.resource_id, parse_date(r.body))
+        return self.record_id
+
+    async def get_resolved_spec(self):
+        # Hydroshare does not provide a history, resolves to repo url
+        return self.get_repo_url()
+
+    async def get_resolved_ref_url(self):
+        # Hydroshare does not provide a history, resolves to repo url
+        return self.get_repo_url()
+
+    def get_repo_url(self):
+        self.resource_id = self._parse_resource_id(self.spec)
+        return "https://www.hydroshare.org/resource/{}".format(self.resource_id)
+
+    def get_build_slug(self):
+        return "hydroshare-{}".format(self.record_id)
+
+
 class GitRepoProvider(RepoProvider):
     """Bare bones git repo provider.
 
diff --git a/binderhub/static/js/index.js b/binderhub/static/js/index.js
old mode 100644
new mode 100755
index 49adcb4c..2465a05e
--- a/binderhub/static/js/index.js
+++ b/binderhub/static/js/index.js
@@ -89,6 +89,11 @@ function updateRepoText() {
     $("#ref").prop("disabled", true);
     $("label[for=ref]").prop("disabled", true);
   }
+  else if (provider === "hydroshare") {
+    text = "Hydroshare resource id or URL";
+    $("#ref").prop("disabled", true);
+    $("label[for=ref]").prop("disabled", true);
+  }
   else if (provider === "dataverse") {
     text = "Dataverse DOI (10.7910/DVN/TJCLKP)";
     $("#ref").prop("disabled", true);
@@ -117,7 +122,8 @@ function getBuildFormValues() {
   }
 
   var ref = $('#ref').val().trim() || 'master';
-  if (providerPrefix === 'zenodo' || providerPrefix === 'figshare' || providerPrefix === 'dataverse') {
+  if (providerPrefix === 'zenodo' || providerPrefix === 'figshare' || providerPrefix === 'dataverse' ||
+      providerPrefix === 'hydroshare') {
     ref = "";
   }
   var path = $('#filepath').val().trim();
diff --git a/binderhub/templates/index.html b/binderhub/templates/index.html
old mode 100644
new mode 100755
index ddedd3d6..f0f140fc
--- a/binderhub/templates/index.html
+++ b/binderhub/templates/index.html
@@ -55,6 +55,7 @@
                 <li class="dropdown-item" value="git"><a href="#">Git repository</a></li>
                 <li class="dropdown-item" value="zenodo"><a href="#">Zenodo DOI</a></li>
                 <li class="dropdown-item" value="figshare"><a href="#">Figshare DOI</a></li>
+                <li class="dropdown-item" value="hydroshare"><a href="#">Hydroshare resource</a></li>
                 <li class="dropdown-item" value="dataverse"><a href="#">Dataverse DOI</a></li>
               </ul>
             </div>
diff --git a/doc/developer/repoproviders.rst b/doc/developer/repoproviders.rst
old mode 100644
new mode 100755
index 2ede2422..c31bcef3
--- a/doc/developer/repoproviders.rst
+++ b/doc/developer/repoproviders.rst
@@ -32,6 +32,8 @@ Currently supported providers, their prefixes and specs are:
     +------------+--------------------+-------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
     | Figshare   | ``figshare``       | ``<figshare-DOI>``                                          | `FigShare <https://figshare.com/>`_ is company that offers hosting for scholarly artifacts (such as code repositories).                   |
     +------------+--------------------+-------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
+    | HydroShare | ``hydroshare``     | ``<hydroshare-DOI-or-ResourceID>``                          | `HydroShare <https://www.hydroshare.org/>`_ hydrologic information system for users to share and publish data and models.                 |
+    +------------+--------------------+-------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
     | Dataverse  | ``dataverse``      | ``<dataverse-DOI>``                                         | `Dataverse <https://dataverse.org/>`_ is open source research data repository software installed all over the world.                      |
     +------------+--------------------+-------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------+
     | Git        | ``git``            | ``<url-escaped-url>/<commit-sha>``                          | A generic repository provider for URLs that point directly to a git repository.                                                           |
diff --git a/doc/reference/repoproviders.rst b/doc/reference/repoproviders.rst
old mode 100644
new mode 100755
index 872cd1e2..d0f5ca37
--- a/doc/reference/repoproviders.rst
+++ b/doc/reference/repoproviders.rst
@@ -52,6 +52,13 @@ Module: :mod:`binderhub.repoproviders`
     :members:
 
 
+:class:`HydroshareProvider`
+---------------------------
+
+.. autoconfigurable:: HydroshareProvider
+    :members:
+
+
 :class:`DataverseProvider`
 ---------------------------
 


Test patch:
diff --git a/binderhub/tests/test_repoproviders.py b/binderhub/tests/test_repoproviders.py
old mode 100644
new mode 100755
index 99296e63..24d9ddeb
--- a/binderhub/tests/test_repoproviders.py
+++ b/binderhub/tests/test_repoproviders.py
@@ -7,7 +7,7 @@ from tornado.ioloop import IOLoop
 from binderhub.repoproviders import (
     tokenize_spec, strip_suffix, GitHubRepoProvider, GitRepoProvider,
     GitLabRepoProvider, GistRepoProvider, ZenodoProvider, FigshareProvider,
-    DataverseProvider
+    HydroshareProvider, DataverseProvider
 )
 
 
@@ -99,6 +99,41 @@ async def test_figshare(spec, resolved_spec, resolved_ref, resolved_ref_url, bui
     assert spec == resolved_spec
 
 
+async def test_hydroshare():
+    spec = 'https://www.hydroshare.org/resource/142c59757ed54de1816777828c9716e7'
+
+    provider = HydroshareProvider(spec=spec)
+
+    ref = await provider.get_resolved_ref()
+    assert ref == '142c59757ed54de1816777828c9716e7.v1545934606'
+
+    slug = provider.get_build_slug()
+    assert slug == 'hydroshare-142c59757ed54de1816777828c9716e7.v1545934606'
+    repo_url = provider.get_repo_url()
+    assert repo_url == spec
+    ref_url = await provider.get_resolved_ref_url()
+    assert ref_url == repo_url
+    resolved_spec = await provider.get_resolved_spec()
+    assert resolved_spec == repo_url
+
+
+async def test_hydroshare_doi():
+    spec = '10.4211/hs.b8f6eae9d89241cf8b5904033460af61'
+
+    provider = HydroshareProvider(spec=spec)
+
+    ref = await provider.get_resolved_ref()
+    assert ref == 'b8f6eae9d89241cf8b5904033460af61.v1565445792'
+
+    slug = provider.get_build_slug()
+    assert slug == 'hydroshare-b8f6eae9d89241cf8b5904033460af61.v1565445792'
+    repo_url = provider.get_repo_url()
+    assert repo_url ==  'https://www.hydroshare.org/resource/b8f6eae9d89241cf8b5904033460af61'
+    ref_url = await provider.get_resolved_ref_url()
+    assert ref_url == repo_url
+    resolved_spec = await provider.get_resolved_spec()
+    assert resolved_spec == repo_url
+
 @pytest.mark.parametrize('spec,resolved_spec,resolved_ref,resolved_ref_url,build_slug', [
     ['10.7910/DVN/TJCLKP',
      '10.7910/DVN/TJCLKP',