Skip to content

Commit

Permalink
add a separate class for figshare FigshareAPIClient
Browse files Browse the repository at this point in the history
  • Loading branch information
MAfarrag committed Jan 4, 2025
1 parent 62ae8a7 commit 06c07d6
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 3 deletions.
176 changes: 175 additions & 1 deletion src/Hapi/parameters/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -11,6 +11,8 @@

import Hapi

BASE_URL = "https://api.figshare.com/v2"

ARTICLE_IDS = [
19999901,
19999988,
Expand Down Expand Up @@ -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)
77 changes: 75 additions & 2 deletions tests/rrm/parameters/test_parameters.py
Original file line number Diff line number Diff line change
@@ -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():
Expand Down Expand Up @@ -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."

0 comments on commit 06c07d6

Please sign in to comment.