Skip to content

Commit

Permalink
implement async client (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenghaoz authored Nov 27, 2022
1 parent f228fa9 commit dc716c0
Show file tree
Hide file tree
Showing 5 changed files with 434 additions and 132 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
name: test (Python ${{ matrix.python-version }})
strategy:
matrix:
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v2
- name: Build the stack
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,20 @@ client.insert_feedbacks([

client.get_recommend('bob', n=10)
```

The Python SDK implements the async client as well:

```python
from gorse import AsyncGorse

client = AsyncGorse('http://127.0.0.1:8087', 'api_key')
await client.insert_feedbacks([
{ 'FeedbackType': 'star', 'UserId': 'bob', 'ItemId': 'vuejs:vue', 'Timestamp': '2022-02-24' },
{ 'FeedbackType': 'star', 'UserId': 'bob', 'ItemId': 'd3:d3', 'Timestamp': '2022-02-25' },
{ 'FeedbackType': 'star', 'UserId': 'bob', 'ItemId': 'dogfalo:materialize', 'Timestamp': '2022-02-26' },
{ 'FeedbackType': 'star', 'UserId': 'bob', 'ItemId': 'mozilla:pdf.js', 'Timestamp': '2022-02-27' },
{ 'FeedbackType': 'star', 'UserId': 'bob', 'ItemId': 'moment:moment', 'Timestamp': '2022-02-28' }
])

await client.get_recommend('bob', n=10)
```
143 changes: 143 additions & 0 deletions gorse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,25 @@
# limitations under the License.
from typing import List, Tuple

import aiohttp
import requests


class GorseException(Exception):
"""
Gorse exception.
"""

def __init__(self, status_code: int, message: str):
self.status_code = status_code
self.message = message


class Gorse:
"""
Gorse client.
"""

def __init__(self, entry_point: str, api_key: str, timeout=None):
self.entry_point = entry_point
self.api_key = api_key
Expand Down Expand Up @@ -155,3 +164,137 @@ def __request(self, method: str, url: str, params=None, json=None) -> dict:
if response.status_code == 200:
return response.json()
raise GorseException(response.status_code, response.text)


class AsyncGorse:
"""
Gorse async client.
"""

def __init__(self, entry_point: str, api_key: str, timeout=None):
self.entry_point = entry_point
self.api_key = api_key
self.timeout = timeout

async def insert_feedback(
self, feedback_type: str, user_id: str, item_id: str, timestamp: str
) -> dict:
"""
Insert a feedback.
"""
return await self.__request(
"POST",
f"{self.entry_point}/api/feedback",
json=[
{
"FeedbackType": feedback_type,
"UserId": user_id,
"ItemId": item_id,
"Timestamp": timestamp,
}
],
)

async def list_feedbacks(self, feedback_type: str, user_id: str):
"""
List feedbacks from a user.
"""
return await self.__request("GET", f"{self.entry_point}/api/user/{user_id}/feedback/{feedback_type}")

async def get_recommend(self, user_id: str, category: str = "", n: int = 10, offset: int = 0, write_back_type: str = None,
write_back_delay: str = None) -> List[str]:
"""
Get recommendation.
"""
payload = {"n": n, "offset": offset}
if write_back_type:
payload["write-back-type"] = write_back_type
if write_back_delay:
payload["write-back-delay"] = write_back_delay
return await self.__request("GET", f"{self.entry_point}/api/recommend/{user_id}/{category}", params=payload)

async def session_recommend(self, feedbacks: list, n: int = 10) -> list:
"""
Get session recommendation.
"""
return await self.__request("POST", f"{self.entry_point}/api/session/recommend?n={n}", json=feedbacks)

async def get_neighbors(self, item_id: str, n: int = 10, offset: int = 0) -> List[str]:
"""
Get item neighbors.
"""
return await self.__request("GET", f"{self.entry_point}/api/item/{item_id}/neighbors?n={n}&offset={offset}")

async def insert_feedbacks(self, feedbacks: list) -> dict:
"""
Insert feedbacks.
"""
return await self.__request("POST", f"{self.entry_point}/api/feedback", json=feedbacks)

async def insert_item(self, item) -> dict:
"""
Insert an item.
"""
return await self.__request("POST", f"{self.entry_point}/api/item", json=item)

async def get_item(self, item_id: str) -> dict:
"""
Get an item.
"""
return await self.__request("GET", f"{self.entry_point}/api/item/{item_id}")

async def get_items(self, n: int, cursor: str = '') -> Tuple[List[dict], str]:
"""
Get items.
:param n: number of returned items
:param cursor: cursor for next page
:return: items and cursor for next page
"""
response = await self.__request(
"GET", f"{self.entry_point}/api/items", params={'n': n, 'cursor': cursor})
return response['Items'], response['Cursor']

async def update_item(self, item_id: str, is_hidden: bool = None, categories: List[str] = None, labels: List[str] = None,
timestamp: str = None,
comment: str = None) -> dict:
"""
Update an item.
"""
return await self.__request("PATCH", f'{self.entry_point}/api/item/{item_id}', json={
"Categories": categories,
"Comment": comment,
"IsHidden": is_hidden,
"Labels": labels,
"Timestamp": timestamp
})

async def delete_item(self, item_id: str) -> dict:
"""
Delete an item.
"""
return await self.__request("DELETE", f"{self.entry_point}/api/item/{item_id}")

async def insert_user(self, user) -> dict:
"""
Insert a user.
"""
return await self.__request("POST", f"{self.entry_point}/api/user", json=user)

async def get_user(self, user_id: str) -> dict:
"""
Get a user.
"""
return await self.__request("GET", f"{self.entry_point}/api/user/{user_id}")

async def delete_user(self, user_id: str) -> dict:
"""
Delete a user.
"""
return await self.__request("DELETE", f"{self.entry_point}/api/user/{user_id}")

async def __request(self, method: str, url: str, params=None, json=None) -> dict:
async with aiohttp.ClientSession() as session:
async with session.request(method, url, params=params, json=json, headers={"X-API-Key": self.api_key}) as response:
if response.status == 200:
return await response.json()
raise GorseException(response.status, await response.text())
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
version='0.4.6',
description='Python SDK for gorse recommender system',
packages=['gorse'],
install_requires=['requests>=2.14.0'],
install_requires=['requests~=2.14.0', 'aiohttp~=3.8.3'],
long_description=long_description,
long_description_content_type='text/markdown'
)
Loading

0 comments on commit dc716c0

Please sign in to comment.