From 06c07d691ba0113881489339597163c0da931967 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Sat, 4 Jan 2025 01:40:00 +0100 Subject: [PATCH] add a separate class for figshare `FigshareAPIClient` --- src/Hapi/parameters/parameters.py | 176 +++++++++++++++++++++++- tests/rrm/parameters/test_parameters.py | 77 ++++++++++- 2 files changed, 250 insertions(+), 3 deletions(-) diff --git a/src/Hapi/parameters/parameters.py b/src/Hapi/parameters/parameters.py index fc7a0025..ffd98618 100644 --- a/src/Hapi/parameters/parameters.py +++ b/src/Hapi/parameters/parameters.py @@ -2,7 +2,7 @@ import json import os -from typing import List, Union +from typing import Dict, List, Optional, Union from urllib.request import urlretrieve import requests @@ -11,6 +11,8 @@ import Hapi +BASE_URL = "https://api.figshare.com/v2" + ARTICLE_IDS = [ 19999901, 19999988, @@ -335,3 +337,175 @@ def _get_headers(token=None): headers["Authorization"] = "token {0}".format(token) return headers + + +class FigshareAPIClient: + """ + A client for interacting with the Figshare API. + + Parameters + ---------- + headers : dict, optional + Headers to include in the API requests, by default None. + + Examples + -------- + >>> client = FigshareAPIClient() + """ + + def __init__(self, headers: Optional[dict] = None): + """initialize.""" + self.base_url = BASE_URL + self.headers = headers or {"Content-Type": "application/json"} + + def send_request( + self, + method: str, + endpoint: str, + data: Optional[dict] = None, + binary: bool = False, + ) -> Dict[str, int]: + """ + Send an HTTP request to the Figshare API. + + Parameters + ---------- + method : str + HTTP method (e.g., 'GET', 'POST'). + endpoint : str + API endpoint to interact with. + data : dict, optional + Payload to include in the request, by default None. + binary : bool, optional + Whether the data payload is binary, by default False. + + Returns + ------- + dict + The parsed JSON response from the API. + + Raises + ------ + requests.exceptions.HTTPError + If the API request fails. + + Examples + -------- + >>> client = FigshareAPIClient() + >>> response = client.send_request("GET", "articles/19999901") #doctest: +SKIP + >>> print(response) #doctest: +SKIP + {'files': [{'id': 35589521, + 'name': '01_TT.tif', + 'size': 1048736, + 'is_link_only': False, + 'download_url': 'https://ndownloader.figshare.com/files/35589521', + 'supplied_md5': '1ddb354132c2f7f54dec6e72bdb62422', + 'computed_md5': '1ddb354132c2f7f54dec6e72bdb62422', + 'mimetype': 'image/tiff'}, + 'authors': [{'id': 11888465, + 'full_name': 'Mostafa Farrag', + 'first_name': 'Mostafa', + 'last_name': 'Farrag', + 'is_active': True, + 'url_name': 'Mostafa_Farrag', + 'orcid_id': '0000-0002-1673-0126'}], + 'figshare_url': 'https://figshare.com/articles/dataset/parameter_set-1/19999901', + 'download_disabled': False, + ... + 'version': 2, + 'status': 'public', + 'size': 19878928, + 'created_date': '2022-06-04T14:15:43Z', + 'modified_date': '2022-06-04T14:15:44Z', + 'is_public': True, + 'is_confidential': False, + 'is_metadata_record': False, + 'confidential_reason': '', + 'metadata_reason': '', + 'license': {'value': 1, + 'name': 'CC BY 4.0', + 'id': 19999901, + 'title': 'Parameter set-1', + 'doi': '10.6084/m9.figshare.19999901.v2', + 'url': 'https://api.figshare.com/v2/articles/19999901', + 'published_date': '2022-06-04T14:15:43Z', + 'url_private_api': 'https://api.figshare.com/v2/account/articles/19999901', + 'url_public_api': 'https://api.figshare.com/v2/articles/19999901', + 'url_private_html': 'https://figshare.com/account/articles/19999901', + 'url_public_html': 'https://figshare.com/articles/dataset/parameter_set-1/19999901', + 'timeline': {'posted': '2022-06-04T14:15:43', + 'firstOnline': '2022-06-04T13:52:54'}, + } + """ + url = f"{self.base_url}/{endpoint}" + payload = json.dumps(data) if data and not binary else data + + try: + response = requests.request(method, url, headers=self.headers, data=payload) + response.raise_for_status() + return response.json() if response.text else None + except requests.exceptions.HTTPError as error: + logger.error(f"HTTPError: {error}, Response: {response.text}") + raise + + def get_article_version(self, article_id: int, version: int) -> Dict[str, int]: + """ + Retrieve a specific version of an article from the Figshare API. + + Parameters + ---------- + article_id : int + The ID of the article to retrieve. + version : int + The version number of the article to retrieve. + + Returns + ------- + dict + Details of the specific version of the article. + + Raises + ------ + requests.exceptions.HTTPError + If the API request fails. + + Examples + -------- + >>> client = FigshareAPIClient() + >>> response = client.get_article_version(19999901, 1) #doctest: +SKIP + >>> print(response) #doctest: +SKIP + """ + endpoint = f"articles/{article_id}/versions/{version}" + return self.send_request("GET", endpoint) + + def list_article_versions(self, article_id: int) -> List[Dict[str, int]]: + """ + Retrieve all available versions of a specific article from the Figshare API. + + Parameters + ---------- + article_id : int + The ID of the article to retrieve versions for. + + Returns + ------- + List[Dict[str, int]]: + A list of available versions for the specified article. + + Raises + ------ + requests.exceptions.HTTPError + If the API request fails. + + Examples + -------- + >>> client = FigshareAPIClient() + >>> versions = client.list_article_versions(19999901) #doctest: +SKIP + >>> print(versions) #doctest: +SKIP + [{'version': 1, + 'url': 'https://api.figshare.com/v2/articles/19999901/versions/1'}, + {'version': 2, + 'url': 'https://api.figshare.com/v2/articles/19999901/versions/2'}] + """ + endpoint = f"articles/{article_id}/versions" + return self.send_request("GET", endpoint) diff --git a/tests/rrm/parameters/test_parameters.py b/tests/rrm/parameters/test_parameters.py index 99b69e7f..b643e018 100644 --- a/tests/rrm/parameters/test_parameters.py +++ b/tests/rrm/parameters/test_parameters.py @@ -1,11 +1,10 @@ import shutil from pathlib import Path from unittest.mock import patch -from urllib.request import urlretrieve import pytest -from Hapi.parameters.parameters import Parameter +from Hapi.parameters.parameters import FigshareAPIClient, Parameter def test_constructor(): @@ -103,3 +102,77 @@ def test_get_parameters(): with patch("Hapi.parameters.parameters.Parameter.get_parameter_set") as mock: parameters.get_parameters() assert mock.call_count == 13 + + +class TestFigshareAPIClient: + + def test_figshare_api_client_get_article(self): + """ + Test an actual API call to Figshare's API to retrieve an article. + + This test requires internet access and valid article IDs. + """ + client = FigshareAPIClient() + response = client.send_request("GET", "articles/19999901") + + # Check basic response structure + assert isinstance(response, dict), "Response should be a dictionary." + assert "id" in response, "Response should include an 'id' key." + assert ( + response["id"] == 19999901 + ), "The article ID should match the requested ID." + + def test_figshare_api_client_get_article_version(self): + """ + Test an actual API call to retrieve a specific version of an article. + + This test requires internet access and valid article IDs. + """ + client = FigshareAPIClient() + response = client.get_article_version(article_id=19999901, version=1) + + # Check basic response structure + assert isinstance(response, dict), "Response should be a dictionary." + assert "id" in response, "Response should include an 'id' key." + assert "version" in response, "Response should include a 'version' key." + assert ( + response["version"] == 1 + ), "The version should match the requested version." + + def test_figshare_api_client_list_article_versions(self): + """ + Test an actual API call to retrieve all available versions of an article. + + This test requires internet access and valid article IDs. + """ + client = FigshareAPIClient() + versions = client.list_article_versions(19999901) + + # Check basic response structure + assert isinstance(versions, list), "Response should be a list." + assert len(versions) > 0, "There should be at least one version." + for version in versions: + assert ( + "version" in version + ), "Each version entry should include a 'version' key." + + def test_figshare_api_client_invalid_article(self): + """ + Test how the API client handles an invalid article ID. + """ + client = FigshareAPIClient() + + with pytest.raises(Exception): + client.send_request("GET", "articles/0") + + def test_figshare_api_client_no_content(self): + """ + Test the API client for an endpoint with no content. + """ + client = FigshareAPIClient() + response = client.send_request( + "GET", "articles" + ) # Assuming this endpoint exists but has no content + + assert response is not None, "Response should not be None." + assert isinstance(response, list), "Response should be a list."