Skip to content

Commit

Permalink
Add async support
Browse files Browse the repository at this point in the history
  • Loading branch information
estheruary committed Nov 18, 2023
1 parent 0a75b06 commit e04bdfb
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 12 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
# CHANGELOG

# 0.0.8

Async Support!

* We now use the `httpx` package to provide sync and asyn support.

```python
import asyncio

from kaginawa import AsyncKaginawa


async def amain():
kagi_client = AsyncKaginawa(...)
res = await kagi_client.generate(...)
print(res.output)

if __name__ == "__main__":
asyncio.run(amain())
```
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@ response: KaginawaSummarizationResponse = client.summarize(
print(response.output)
```


## Async!

```python
import asyncio
from kaginawa.async_client import AsyncKaginawa

async def amain():
kagi_client = AsyncKaginawa(...)
res = await kagi_client.generate(...)
print(res.output)

# If you want to explicitly close the client.
kagi_client.close()

if __name__ == "__main__":
asyncio.run(amain())
```

## FAQ

<dl>
Expand Down
2 changes: 1 addition & 1 deletion kaginawa/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from pkg_resources import parse_version

__version__ = parse_version("0.0.7")
__version__ = parse_version("0.0.8")
206 changes: 206 additions & 0 deletions kaginawa/async_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import json
import os
from typing import Optional

import httpx

from kaginawa.exceptions import KaginawaError
from kaginawa.models import (
KaginawaEnrichResponse,
KaginawaFastGPTResponse,
KaginawaSummarizationResponse,
)


class AsyncKaginawa:
"""The main client to the Kagi API"""

def __init__(
self,
token: Optional[str] = None,
session: Optional[httpx.AsyncClient] = None,
api_base: str = "https://kagi.com/api",
):
"""Create a new instance of the Kagi API wrapper.
Parameters:
token (Optional[str], optional): The API access token to authenticate
httpx. If this value is unset the library will attempt to read the
environment variable KAGI_API_KEY. If both are unset an exception will
be raised.
session (Optional[httpx.Client], optional): An optional `httpx`
session object to use for sending HTTP httpx. Defaults to `None`.
api_base (str, optional): The base URL for the Kagi API endpoint.
Defaults to "https://kagi.com/api".
"""

try:
token = token or os.environ["KAGI_API_KEY"]
except KeyError as e:
raise KaginawaError("Value for `token` not given and env KAGI_API_KEY is unset") from e

self.token = token
self.api_base = api_base

if not session:
session = httpx.AsyncClient()

self.session = session

self.session.headers = {"Authorization": f"Bot {self.token}"}

async def close(self):
return await self.session.aclose()

async def generate(self, query: str, cache: Optional[bool] = None):
"""Generate a FastGPT response from a text query.
Parameters:
query (str): The prompt to send to FastGPT.
cache (Optional[bool], optional): Allow cached responses. Defaults to `None`.
Returns:
KaginawaFastGPTResponse: A model representing the response.
"""
try:
optional_params = {}

if cache is not None:
optional_params["cache"] = cache

res = await self.session.post(
f"{self.api_base}/v0/fastgpt",
json={
"query": query,
**optional_params,
},
)

res.raise_for_status()

raw_response = res.json()
print(json.dumps(raw_response, indent=2, sort_keys=True))
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/fastgpt") from e

return KaginawaFastGPTResponse.from_raw(raw_response)

async def enrich_web(self, query: str):
"""Query the Teclis index for relevant web results for a given query.
Parameters:
query (str): The search query.
Returns:
KaginawaEnrichWebResponse: A model representing the response.
"""

try:
res = await self.session.get(
f"{self.api_base}/v0/enrich/web",
params={"q": query},
)
res.raise_for_status()

raw_response = res.json()
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/enrich/web") from e

return KaginawaEnrichResponse.from_raw(raw_response).results[0]

async def enrich_news(self, query: str):
"""Query the Teclis index for relevant web results for a given query.
Parameters:
query (str): The search query.
Returns:
KaginawaEnrichWebResponse: A model representing the response.
"""

try:
res = await self.session.get(
f"{self.api_base}/v0/enrich/news",
params={"q": query},
)
res.raise_for_status()

raw_response = res.json()
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/enrich/web") from e

return KaginawaEnrichResponse.from_raw(raw_response).results[0]

async def summarize(
self,
url: Optional[str] = None,
text: Optional[str] = None,
engine: Optional[str] = None,
summary_type: Optional[str] = None,
target_language: Optional[str] = None,
cache: Optional[bool] = None,
):
"""Summarize a URL or text snippet.
Parameters:
url (Optional[str], optional): The URL to summarize. This option is exclusive
with the `text` parameter. Defaults to `None`.
text (Optional[str], optional): The text snippet to summarize. This option is
exclusive with the `url` parameter. Defaults to `None`.
engine (Optional[str], optional): The summarization engine to use. There is a
helper enum `KaginawaSummarizationEngine` with the valid values for this
parameter. Defaults to `None`.
summary_type (Optional[str], optional): The 'kind' of summary you would like
the model to use. There is a helper enum KaginawaSummaryType with the valid
values for this parameter. Defaults to `None`.
target_language: (Optional[str], optional): The language CODE (eg. EN, ZH, FR)
corresponding to the language you would like the summary in. See
https://help.kagi.com/kagi/api/summarizer.html for valid language codes.
Defaults to `None`.
cache (Optional[bool], optional): Allow cached responses. Defaults to `None`.
Returns:
KaginawaSummarizationResponse: A model representing the response.
"""
try:
params = {}

if not (bool(url) ^ bool(text)):
raise KaginawaError("You must provide exactly one of 'url' or 'text'.")

if url:
params["url"] = url

if text:
params["text"] = text

if engine:
params["engine"] = engine

if summary_type:
params["summary_type"] = summary_type

if target_language:
params["target_language"] = target_language

if cache is not None:
params["cache"] = cache

res = await self.session.post(
f"{self.api_base}/v0/summarize",
data=params,
)

raw_response = res.json()
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/summarize") from e

return KaginawaSummarizationResponse.from_raw(raw_response)
22 changes: 12 additions & 10 deletions kaginawa/client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import json
import os
from typing import Optional

import requests
import httpx

from kaginawa.exceptions import KaginawaError
from kaginawa.models import (
Expand All @@ -17,19 +18,19 @@ class Kaginawa:
def __init__(
self,
token: Optional[str] = None,
session: Optional[requests.Session] = None,
session: Optional[httpx.Client] = None,
api_base: str = "https://kagi.com/api",
):
"""Create a new instance of the Kagi API wrapper.
Parameters:
token (Optional[str], optional): The API access token to authenticate
requests. If this value is unset the library will attempt to read the
httpx. If this value is unset the library will attempt to read the
environment variable KAGI_API_KEY. If both are unset an exception will
be raised.
session (Optional[requests.Session], optional): An optional `requests`
session object to use for sending HTTP requests. Defaults to `None`.
session (Optional[httpx.Client], optional): An optional `httpx`
session object to use for sending HTTP httpx. Defaults to `None`.
api_base (str, optional): The base URL for the Kagi API endpoint.
Defaults to "https://kagi.com/api".
Expand All @@ -44,7 +45,7 @@ def __init__(
self.api_base = api_base

if not session:
session = requests.Session()
session = httpx.Client()

self.session = session

Expand Down Expand Up @@ -78,7 +79,8 @@ def generate(self, query: str, cache: Optional[bool] = None):
res.raise_for_status()

raw_response = res.json()
except requests.RequestException as e:
print(json.dumps(raw_response, indent=2, sort_keys=True))
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/fastgpt") from e

return KaginawaFastGPTResponse.from_raw(raw_response)
Expand All @@ -101,7 +103,7 @@ def enrich_web(self, query: str):
res.raise_for_status()

raw_response = res.json()
except requests.RequestException as e:
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/enrich/web") from e

return KaginawaEnrichResponse.from_raw(raw_response).results[0]
Expand All @@ -124,7 +126,7 @@ def enrich_news(self, query: str):
res.raise_for_status()

raw_response = res.json()
except requests.RequestException as e:
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/enrich/web") from e

return KaginawaEnrichResponse.from_raw(raw_response).results[0]
Expand Down Expand Up @@ -195,7 +197,7 @@ def summarize(
)

raw_response = res.json()
except requests.RequestException as e:
except httpx.HTTPError as e:
raise KaginawaError("Error calling /v0/summarize") from e

return KaginawaSummarizationResponse.from_raw(raw_response)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
requests>=2
httpx>=0.25.0

0 comments on commit e04bdfb

Please sign in to comment.