Skip to content

Commit

Permalink
feat: ✨ add debug mode
Browse files Browse the repository at this point in the history
Resolves: #73
  • Loading branch information
danielnsilva committed Dec 1, 2023
1 parent 97ac08c commit fb249e9
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 5 deletions.
51 changes: 50 additions & 1 deletion semanticscholar/ApiRequester.py
Expand Up @@ -3,6 +3,8 @@
import httpx
import asyncio
import warnings
import inspect
import json
from tenacity import (retry, retry_if_exception_type, stop_after_attempt,
wait_fixed)

Expand All @@ -12,12 +14,14 @@

class ApiRequester:

def __init__(self, timeout) -> None:
def __init__(self, timeout, debug) -> None:
'''
:param float timeout: an exception is raised \
if the server has not issued a response for timeout seconds.
:param bool debug: enable debug mode.
'''
self.timeout = timeout
self.debug = debug

@property
def timeout(self) -> int:
Expand All @@ -32,6 +36,48 @@ def timeout(self, timeout: int) -> None:
:param int timeout:
'''
self._timeout = timeout

@property
def debug(self) -> bool:
'''
:type: :class:`bool`
'''
return self._debug

@debug.setter
def debug(self, debug: bool) -> None:
'''
:param bool debug:
'''
self._debug = debug

def _curl_cmd(self, url: str, method: str, headers: dict, payload: dict = None) -> str:
curl_cmd = f'curl -X {method}'
for key, value in headers.items():
curl_cmd += f' -H \'{key}: {value}\''
curl_cmd += f' -d \'{json.dumps(payload)}\'' if payload else ''
curl_cmd += f' {url}'
return curl_cmd

def _get_caller_function_name(self) -> str:
stack = inspect.stack()
# for item in stack:
# print(inspect.getframeinfo(item[0]).function)
caller = stack[5]
frame = caller[0]
info = inspect.getframeinfo(frame)
return info.function

def _print_debug(self, url, headers, payload, method) -> None:
print('-' * 80)
print(f'Caller function: {self._get_caller_function_name()}')
print('-' * 80)
print(f'Method: {method}\n')
print(f'URL:\n{url}\n')
print(f'Headers:\n{headers}\n')
print(f'Payload:\n{payload}\n')
print(f'cURL command:\n{self._curl_cmd(url, method, headers, payload)}')
print('-' * 80)

@retry(
wait=wait_fixed(30),
Expand All @@ -58,6 +104,9 @@ async def get_data_async(
url = f'{url}?{parameters.lstrip("&")}'
method = 'POST' if payload else 'GET'

if self.debug:
self._print_debug(url, headers, payload, method)

async with httpx.AsyncClient() as client:
r = await client.request(
method, url, timeout=self._timeout, headers=headers, json=payload)
Expand Down
22 changes: 20 additions & 2 deletions semanticscholar/AsyncSemanticScholar.py
Expand Up @@ -25,13 +25,15 @@ def __init__(
self,
timeout: int = 10,
api_key: str = None,
api_url: str = None
api_url: str = None,
debug: bool = False
) -> None:
'''
:param float timeout: (optional) an exception is raised\
if the server has not issued a response for timeout seconds.
:param str api_key: (optional) private API key.
:param str api_url: (optional) custom API url.
:param bool debug: (optional) enable debug mode.
'''

if api_url:
Expand All @@ -43,7 +45,8 @@ def __init__(
self.auth_header = {'x-api-key': api_key}

self._timeout = timeout
self._requester = ApiRequester(self._timeout)
self._debug = debug
self._requester = ApiRequester(self._timeout, self._debug)

@property
def timeout(self) -> int:
Expand All @@ -59,6 +62,21 @@ def timeout(self, timeout: int) -> None:
'''
self._timeout = timeout
self._requester.timeout = timeout

@property
def debug(self) -> bool:
'''
:type: :class:`bool`
'''
return self._debug

@debug.setter
def debug(self, debug: bool) -> None:
'''
:param bool debug:
'''
self._debug = debug
self._requester.debug = debug

async def get_paper(
self,
Expand Down
23 changes: 21 additions & 2 deletions semanticscholar/SemanticScholar.py
Expand Up @@ -17,20 +17,24 @@ def __init__(
self,
timeout: int = 10,
api_key: str = None,
api_url: str = None
api_url: str = None,
debug: bool = False
) -> None:
'''
:param float timeout: (optional) an exception is raised\
if the server has not issued a response for timeout seconds.
:param str api_key: (optional) private API key.
:param str api_url: (optional) custom API url.
:param bool debug: (optional) enable debug mode.
'''
nest_asyncio.apply()
self._timeout = timeout
self._debug = debug
self._AsyncSemanticScholar = AsyncSemanticScholar(
timeout=timeout,
api_key=api_key,
api_url=api_url
api_url=api_url,
debug=debug
)

@property
Expand All @@ -47,6 +51,21 @@ def timeout(self, timeout: int) -> None:
'''
self._timeout = timeout
self._AsyncSemanticScholar.timeout = timeout

@property
def debug(self) -> bool:
'''
:type: :class:`bool`
'''
return self._debug

@debug.setter
def debug(self, debug: bool) -> None:
'''
:param bool debug:
'''
self._debug = debug
self._AsyncSemanticScholar.debug = debug

def get_paper(
self,
Expand Down
17 changes: 17 additions & 0 deletions tests/data/debug_output.txt
@@ -0,0 +1,17 @@
--------------------------------------------------------------------------------
Caller function: get_papers
--------------------------------------------------------------------------------
Method: POST

URL:
https://api.semanticscholar.org/graph/v1/paper/batch?fields=abstract,authors,citationCount,corpusId,externalIds,fieldsOfStudy,influentialCitationCount,isOpenAccess,journal,openAccessPdf,paperId,publicationDate,publicationTypes,publicationVenue,referenceCount,s2FieldsOfStudy,title,url,venue,year

Headers:
{'x-api-key': 'F@k3K3y'}

Payload:
{'ids': ['CorpusId:470667', '10.2139/ssrn.2250500', '0f40b1f08821e22e859c6050916cec3667778613']}

cURL command:
curl -X POST -H 'x-api-key: F@k3K3y' -d '{"ids": ["CorpusId:470667", "10.2139/ssrn.2250500", "0f40b1f08821e22e859c6050916cec3667778613"]}' https://api.semanticscholar.org/graph/v1/paper/batch?fields=abstract,authors,citationCount,corpusId,externalIds,fieldsOfStudy,influentialCitationCount,isOpenAccess,journal,openAccessPdf,paperId,publicationDate,publicationTypes,publicationVenue,referenceCount,s2FieldsOfStudy,title,url,venue,year
--------------------------------------------------------------------------------
50 changes: 50 additions & 0 deletions tests/data/test_debug.yaml
@@ -0,0 +1,50 @@
interactions:
- request:
body: '{"ids": ["CorpusId:470667", "10.2139/ssrn.2250500", "0f40b1f08821e22e859c6050916cec3667778613"]}'
headers:
accept:
- '*/*'
accept-encoding:
- gzip, deflate
connection:
- keep-alive
content-length:
- '96'
content-type:
- application/json
host:
- api.semanticscholar.org
user-agent:
- python-httpx/0.25.1
x-api-key:
- F@k3K3y
method: POST
uri: https://api.semanticscholar.org/graph/v1/paper/batch?fields=abstract,authors,citationCount,corpusId,externalIds,fieldsOfStudy,influentialCitationCount,isOpenAccess,journal,openAccessPdf,paperId,publicationDate,publicationTypes,publicationVenue,referenceCount,s2FieldsOfStudy,title,url,venue,year
response:
content: '{"message":"Forbidden"}'
headers:
Connection:
- keep-alive
Content-Length:
- '23'
Content-Type:
- application/json
Date:
- Fri, 01 Dec 2023 17:42:22 GMT
Via:
- 1.1 0e3b340d2469d5d03795669e20d22b7e.cloudfront.net (CloudFront)
X-Amz-Cf-Id:
- 5GOaOSmWlLCOLN3BYU8v4ZYeCVwcLY3rdViECkesvkiObdK3RVAAKw==
X-Amz-Cf-Pop:
- GRU3-P4
X-Cache:
- Error from cloudfront
x-amz-apigw-id:
- PRkn3G9vvHcETAQ=
x-amzn-ErrorType:
- ForbiddenException
x-amzn-RequestId:
- b5de114b-ac94-4023-9e40-17bf4713124d
http_version: HTTP/1.1
status_code: 403
version: 1
20 changes: 20 additions & 0 deletions tests/test_semanticscholar.py
@@ -1,4 +1,6 @@
import io
import json
import sys
import unittest
import asyncio
from datetime import datetime
Expand Down Expand Up @@ -411,6 +413,24 @@ def test_empty_paginated_results(self):
data = self.sch.search_paper('n0 r3sult s3arch t3rm')
self.assertEqual(data.total, 0)

@test_vcr.use_cassette
def test_debug(self):
with open('tests/data/debug_output.txt', 'r') as file:
expected_output = file.read()
captured_stdout = io.StringIO()
sys.stdout = captured_stdout
self.sch = SemanticScholar(debug=True, api_key='F@k3K3y')
self.assertEqual(self.sch.debug, True)
list_of_paper_ids = [
'CorpusId:470667',
'10.2139/ssrn.2250500',
'0f40b1f08821e22e859c6050916cec3667778613']
with self.assertRaises(PermissionError):
self.sch.get_papers(list_of_paper_ids)
sys.stdout = sys.__stdout__
self.assertEqual(captured_stdout.getvalue().strip(),
expected_output.strip())

class AsyncSemanticScholarTest(unittest.IsolatedAsyncioTestCase):

def setUp(self) -> None:
Expand Down

0 comments on commit fb249e9

Please sign in to comment.