diff --git a/binderhub/repoproviders.py b/binderhub/repoproviders.py index ebd937bd2..4bd2041a5 100644 --- a/binderhub/repoproviders.py +++ b/binderhub/repoproviders.py @@ -357,18 +357,38 @@ class FigshareProvider(RepoProvider): "ref": {"enabled": False}, } - url_regex = re.compile(r"(.*)/articles/([^/]+)/(\d+)(/)?(\d+)?") - async def get_resolved_ref(self): client = AsyncHTTPClient() req = HTTPRequest(f"https://doi.org/{self.spec}", user_agent="BinderHub") + # fetch doi: will 404 if it doesn't exist r = await client.fetch(req) + # parse doi, not figshare url + _doi_n, _, identifier = self.spec.partition("/") + doi_fields = identifier.split(".") + # 2 cases: + # versioned: 10.6084/m9.figshare.9782777.v1 + # unversioned: 10.6084/m9.figshare.9782777 + if len(doi_fields) < 3: + raise ValueError(f"Unexpected figshare doi: {self.spec}") + if doi_fields[-1].startswith("v"): + article_version = doi_fields[-1][1:] + article_id = doi_fields[-2] + else: + # unversioned, get latest version from API + # unversioned defaults to latest, + # and figshare doesn't put version in url, + # so resolve latest version from api + article_id = doi_fields[-1] + r = await client.fetch( + f"https://api.figshare.com/v2/articles/{article_id}/versions", + user_agent="BinderHub", + ) + # list of versions containing {"version": 2} + article_versions = json.loads(r.body) + if not article_versions: + raise ValueError(f"Article {article_id} has no versions") + article_version = sorted(v["version"] for v in article_versions)[-1] - match = self.url_regex.match(r.effective_url) - article_id = match.groups()[2] - article_version = match.groups()[4] - if not article_version: - article_version = "1" self.record_id = f"{article_id}.v{article_version}" return self.record_id diff --git a/binderhub/tests/http-record.api.figshare.com.json b/binderhub/tests/http-record.api.figshare.com.json new file mode 100644 index 000000000..351bb8ed0 --- /dev/null +++ b/binderhub/tests/http-record.api.figshare.com.json @@ -0,0 +1,17 @@ +{ + "https://api.figshare.com/v2/articles/9782777/versions": { + "body": "[{\"version\": 1, \"url\": \"https://api.figshare.com/v2/articles/9782777/versions/1\"}, {\"version\": 2, \"url\": \"https://api.figshare.com/v2/articles/9782777/versions/2\"}, {\"version\": 3, \"url\": \"https://api.figshare.com/v2/articles/9782777/versions/3\"}]", + "code": 200, + "headers": { + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Headers": "Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,X-API-Revision", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS, DELETE, PUT", + "Access-Control-Expose-Headers": "Location", + "Content-Encoding": "gzip", + "Content-Type": "application/json", + "Date": "Fri, 10 Apr 2026 22:03:18 GMT", + "Server": "nginx", + "Vary": "Accept-Encoding" + } + } +} diff --git a/binderhub/tests/test_repoproviders.py b/binderhub/tests/test_repoproviders.py index 124ece413..0bfb02e38 100644 --- a/binderhub/tests/test_repoproviders.py +++ b/binderhub/tests/test_repoproviders.py @@ -105,13 +105,13 @@ async def test_zenodo(spec, resolved_spec, resolved_ref, resolved_ref_url, build "https://doi.org/10.6084/m9.figshare.9782777.v1", "figshare-9782777.v1", ], - # spec without version is accepted as version 1 - check FigshareProvider.get_resolved_ref() + # spec without version is accepted as latest version - check FigshareProvider.get_resolved_ref() [ "10.6084/m9.figshare.9782777", - "10.6084/m9.figshare.9782777.v1", - "9782777.v1", - "https://doi.org/10.6084/m9.figshare.9782777.v1", - "figshare-9782777.v1", + "10.6084/m9.figshare.9782777.v3", + "9782777.v3", + "https://doi.org/10.6084/m9.figshare.9782777.v3", + "figshare-9782777.v3", ], ], ) diff --git a/dev-requirements.txt b/dev-requirements.txt index 1d909a09b..da4f1554d 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -4,7 +4,7 @@ chartpress>=2.1 click dockerspawner jsonschema -jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.06.0 +jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.07.2 jupyter_packaging>=0.10.4,<2 jupyterhub @ git+https://github.com/RCOSDP/CS-jupyterhub.git@5.2.1 nest-asyncio diff --git a/helm-chart/images/binderhub/requirements.in b/helm-chart/images/binderhub/requirements.in index bbb603978..db0832b1a 100644 --- a/helm-chart/images/binderhub/requirements.in +++ b/helm-chart/images/binderhub/requirements.in @@ -12,7 +12,7 @@ google-cloud-logging==3.* jupyterhub @ git+https://github.com/RCOSDP/CS-jupyterhub.git@5.2.1 # jupyter-repo2docker is pinned to CS-repo2docker -jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.06.0 +jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.07.2 # Workaround: Added a dependecy of ruamel-yaml because jupyter-telemetry, # which has a dependency of ruamel-yaml, was removed from jupyterhub. diff --git a/helm-chart/images/binderhub/requirements.txt b/helm-chart/images/binderhub/requirements.txt index fa6f1c335..761a59833 100644 --- a/helm-chart/images/binderhub/requirements.txt +++ b/helm-chart/images/binderhub/requirements.txt @@ -127,7 +127,7 @@ jsonschema-specifications==2025.9.1 # via jsonschema jupyter-events==0.12.0 # via jupyterhub -jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.06.0 +jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.07.2 # via # -r helm-chart/images/binderhub/../../../requirements.txt # -r helm-chart/images/binderhub/requirements.in diff --git a/requirements.txt b/requirements.txt index 064a84e5a..d72effd10 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,7 @@ escapism jinja2 jsonschema jupyterhub @ git+https://github.com/RCOSDP/CS-jupyterhub.git@5.2.1 -jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.06.0 +jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.07.2 kubernetes prometheus_client pyjwt>=2 diff --git a/testing/local-binder-local-hub/requirements.txt b/testing/local-binder-local-hub/requirements.txt index 6cb118686..fcd621df3 100644 --- a/testing/local-binder-local-hub/requirements.txt +++ b/testing/local-binder-local-hub/requirements.txt @@ -1,3 +1,3 @@ dockerspawner>=12 -jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.06.0 +jupyter-repo2docker @ git+https://github.com/RCOSDP/CS-repo2docker.git@2026.07.2 jupyterhub @ git+https://github.com/RCOSDP/CS-jupyterhub.git@5.2.1