Skip to content

Commit 0d11a69

Browse files
committed
feat: Add API key support as alternative authentication method
1 parent 892eb6a commit 0d11a69

File tree

4 files changed

+135
-50
lines changed

4 files changed

+135
-50
lines changed

asknews_sdk/client.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from asknews_sdk.errors import raise_from_response
1818
from asknews_sdk.response import APIResponse, AsyncAPIResponse
1919
from asknews_sdk.security import (
20+
APIKey,
2021
AsyncTokenLoadHook,
2122
AsyncTokenSaveHook,
2223
OAuth2ClientCredentials,
@@ -47,6 +48,7 @@ def __init__(
4748
client_id: Optional[str],
4849
client_secret: Optional[str],
4950
scopes: Optional[Set[str]],
51+
api_key: Optional[str],
5052
base_url: str,
5153
token_url: str,
5254
verify_ssl: bool,
@@ -64,6 +66,7 @@ def __init__(
6466
self.client_id = client_id
6567
self.client_secret = client_secret
6668
self.scopes = {"offline", "openid", *(scopes or set())}
69+
self.api_key = api_key
6770
self.base_url = base_url
6871
self.token_url = token_url
6972
self.verify_ssl = verify_ssl
@@ -74,18 +77,25 @@ def __init__(
7477

7578
if auth:
7679
if auth is CLIENT_DEFAULT:
77-
assert self.client_id and self.client_secret, (
78-
"client_id and client_secret are required for client credentials"
79-
)
80-
81-
self._client_auth = OAuth2ClientCredentials(
82-
client_id=self.client_id,
83-
client_secret=self.client_secret,
84-
token_url=self.token_url,
85-
scopes=self.scopes,
86-
_token_load_hook=_token_load_hook,
87-
_token_save_hook=_token_save_hook,
88-
)
80+
if self.api_key is not None:
81+
self._client_auth = APIKey(
82+
api_key=self.api_key,
83+
)
84+
elif self.client_id is not None and self.client_secret is not None:
85+
self._client_auth = OAuth2ClientCredentials(
86+
client_id=self.client_id,
87+
client_secret=self.client_secret,
88+
token_url=self.token_url,
89+
scopes=self.scopes,
90+
_token_load_hook=_token_load_hook,
91+
_token_save_hook=_token_save_hook,
92+
)
93+
else:
94+
raise ValueError(
95+
"Either api_key or client_id and client_secret are "
96+
"required for authentication. To explicitly disable "
97+
"authentication, set auth=None.",
98+
)
8999
else:
90100
assert not isinstance(auth, Sentinel)
91101
self._client_auth = auth
@@ -140,11 +150,13 @@ class APIClient(BaseAPIClient[Client, APIResponse]):
140150
Sync HTTP API Client
141151
142152
:param client_id: Client ID
143-
:type client_id: str
153+
:type client_id: Optional[str]
144154
:param client_secret: Client secret
145-
:type client_secret: str
155+
:type client_secret: Optional[str]
146156
:param scopes: OAuth scopes
147-
:type scopes: Set[str]
157+
:type scopes: Optional[Set[str]]
158+
:param api_key: API key
159+
:type api_key: Optional[str]
148160
:param base_url: Base URL
149161
:type base_url: str
150162
:param token_url: Token URL
@@ -166,6 +178,7 @@ def __init__(
166178
client_id: Optional[str],
167179
client_secret: Optional[str],
168180
scopes: Optional[Set[str]],
181+
api_key: Optional[str],
169182
base_url: str,
170183
token_url: str,
171184
verify_ssl: bool = True,
@@ -184,6 +197,7 @@ def __init__(
184197
client_id=client_id,
185198
client_secret=client_secret,
186199
scopes=scopes,
200+
api_key=api_key,
187201
base_url=base_url,
188202
token_url=token_url,
189203
verify_ssl=verify_ssl,
@@ -286,11 +300,13 @@ class AsyncAPIClient(BaseAPIClient[AsyncClient, AsyncAPIResponse]):
286300
Base Async HTTP API Client
287301
288302
:param client_id: Client ID
289-
:type client_id: str
303+
:type client_id: Optional[str]
290304
:param client_secret: Client secret
291-
:type client_secret: str
305+
:type client_secret: Optional[str]
292306
:param scopes: OAuth scopes
293-
:type scopes: Set[str]
307+
:type scopes: Optional[Set[str]]
308+
:param api_key: API key
309+
:type api_key: Optional[str]
294310
:param base_url: Base URL
295311
:type base_url: str
296312
:param token_url: Token URL
@@ -312,6 +328,7 @@ def __init__(
312328
client_id: Optional[str],
313329
client_secret: Optional[str],
314330
scopes: Optional[Set[str]],
331+
api_key: Optional[str],
315332
base_url: str,
316333
token_url: str,
317334
verify_ssl: bool = True,
@@ -330,6 +347,7 @@ def __init__(
330347
client_id=client_id,
331348
client_secret=client_secret,
332349
scopes=scopes,
350+
api_key=api_key,
333351
base_url=base_url,
334352
token_url=token_url,
335353
verify_ssl=verify_ssl,
@@ -393,7 +411,7 @@ async def request(
393411
:param stream_type: Stream type
394412
:type stream_type: StreamType
395413
:return: AsyncAPIResponse object
396-
:rtype: APIResponse
414+
:rtype: AsyncAPIResponse
397415
"""
398416
response: Response = await self._client.send(
399417
self.build_api_request(

asknews_sdk/dto/alert.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ class ReportRequest(BaseModel):
166166
"If not provided, the default report prompt will be used. You can use {summaries} "
167167
"to insert the prompt optimized summaries into your report query."
168168
),
169-
example=[
169+
examples=[
170170
[
171171
"system",
172172
"You are a helpful AI bot. Write a report based on summaries provided by the user.",
@@ -175,7 +175,7 @@ class ReportRequest(BaseModel):
175175
],
176176
)
177177
model: Optional[ReportModel] = Field(
178-
None, description="The model to use for the report", example="gpt-4o"
178+
None, description="The model to use for the report", examples=["gpt-4o"]
179179
)
180180
logo_url: Optional[HttpUrlString] = Field(
181181
None, description="The logo URL to use for the report"
@@ -189,20 +189,26 @@ class CreateAlertRequest(BaseSchema):
189189
"The query to run for the alert. For example you ask for "
190190
'"I want to be alerted if there is a protest in paris"'
191191
),
192-
example="I want to be alerted if the president of the US says something about the economy",
192+
examples=[
193+
"I want to be alerted if the president of the US says something about the economy",
194+
],
193195
)
194196
cron: CronStr = Field(
195197
...,
196198
description=(
197199
"The cron schedule for the alert. For example hourly is '0 * * * *'."
198200
" See https://crontab.run/ for more examples"
199201
),
200-
example="*/15 * * * *",
202+
examples=[
203+
"*/15 * * * *",
204+
],
201205
)
202206
model: Optional[CheckAlertModel] = Field(
203207
...,
204208
description="The model to use for the alert check",
205-
example="meta-llama/Meta-Llama-3.1-8B-Instruct",
209+
examples=[
210+
"meta-llama/Meta-Llama-3.1-8B-Instruct",
211+
],
206212
)
207213
share_link: Optional[HttpUrlString] = Field(
208214
None, description="The newsplunker share link to update when the alert triggers"
@@ -243,20 +249,26 @@ class UpdateAlertRequest(BaseSchema):
243249
"The query to run for the alert. For example you ask for "
244250
'"I want to be alerted if there is a protest in paris"'
245251
),
246-
example="I want to be alerted if the president of the US says something about the economy",
252+
examples=[
253+
"I want to be alerted if the president of the US says something about the economy",
254+
],
247255
)
248256
cron: Optional[CronStr] = Field(
249257
None,
250258
description=(
251259
"The cron schedule for the alert. For example hourly is '0 * * * *'."
252260
" See https://crontab.run/ for more examples"
253261
),
254-
example="*/15 * * * *",
262+
examples=[
263+
"*/15 * * * *",
264+
],
255265
)
256266
model: Optional[CheckAlertModel] = Field(
257267
None,
258268
description="The model to use for the alert check",
259-
example="meta-llama/Meta-Llama-3.1-8B-Instruct",
269+
examples=[
270+
"meta-llama/Meta-Llama-3.1-8B-Instruct",
271+
],
260272
)
261273
share_link: Optional[HttpUrlString] = Field(
262274
None, description="The newsplunker share link to update when the alert triggers"

asknews_sdk/sdk.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,30 @@ class AskNewsSDK:
3636
3737
Usage:
3838
39+
```python
40+
>>> with AskNewsSDK(api_key=...) as sdk:
41+
>>> stories_response = sdk.stories.get_stories(...)
42+
```
43+
44+
Or
45+
3946
```python
4047
>>> with AskNewsSDK(client_id=..., client_secret=...) as sdk:
4148
>>> stories_response = sdk.stories.get_stories(...)
4249
```
4350
44-
:param client_id: The client ID for your AskNews API application.
45-
:type client_id: str
46-
:param client_secret: The client secret for your AskNews API application.
47-
:type client_secret: str
48-
:param scopes: The scopes to request for your AskNews API application.
51+
:param client_id: The client ID for the AskNews API credentials.
52+
:type client_id: Optional[str]
53+
:param client_secret: The client secret for the AskNews API credentials.
54+
:type client_secret: Optional[str]
55+
:param scopes: The scopes to request for the AskNews API credentials.
4956
:type scopes: Optional[Set[str]]
5057
:param base_url: The base URL for the AskNews API.
5158
:type base_url: str
5259
:param token_url: The token URL for the AskNews API.
5360
:type token_url: str
61+
:param api_key: An API key for the AskNews API.
62+
:type api_key: Optional[str]
5463
:param verify_ssl: Whether or not to verify SSL certificates.
5564
:type verify_ssl: bool
5665
:param retries: The number of retries to attempt on connection errors.
@@ -72,6 +81,7 @@ def __init__(
7281
scopes: Optional[Set[str]] = DEFAULT_SCOPES,
7382
base_url: str = DEFAULT_API_BASE_URL,
7483
token_url: str = DEFAULT_TOKEN_URL,
84+
api_key: Optional[str] = None,
7585
verify_ssl: bool = True,
7686
retries: int = 3,
7787
timeout: Optional[float] = None,
@@ -87,6 +97,7 @@ def __init__(
8797
client_id=client_id,
8898
client_secret=client_secret,
8999
scopes=scopes,
100+
api_key=api_key,
90101
base_url=base_url,
91102
token_url=token_url,
92103
verify_ssl=verify_ssl,
@@ -134,21 +145,30 @@ class AsyncAskNewsSDK:
134145
135146
Usage:
136147
148+
```python
149+
>>> async with AskNewsSDK(api_key=...) as sdk:
150+
>>> stories_response = await sdk.stories.get_stories(...)
151+
```
152+
153+
Or
154+
137155
```python
138156
>>> async with AskNewsSDK(client_id=..., client_secret=...) as sdk:
139157
>>> stories_response = await sdk.stories.get_stories(...)
140158
```
141159
142-
:param client_id: The client ID for your AskNews API application.
143-
:type client_id: str
144-
:param client_secret: The client secret for your AskNews API application.
145-
:type client_secret: str
146-
:param scopes: The scopes to request for your AskNews API application.
160+
:param client_id: The client ID for the AskNews API credentials.
161+
:type client_id: Optional[str]
162+
:param client_secret: The client secret for the AskNews API credentials.
163+
:type client_secret: Optional[str]
164+
:param scopes: The scopes to request for the AskNews API credentials.
147165
:type scopes: Optional[Set[str]]
148166
:param base_url: The base URL for the AskNews API.
149167
:type base_url: str
150168
:param token_url: The token URL for the AskNews API.
151169
:type token_url: str
170+
:param api_key: An API key for the AskNews API.
171+
:type api_key: Optional[str]
152172
:param verify_ssl: Whether or not to verify SSL certificates.
153173
:type verify_ssl: bool
154174
:param retries: The number of retries to attempt on connection errors.
@@ -170,6 +190,7 @@ def __init__(
170190
scopes: Optional[Set[str]] = DEFAULT_SCOPES,
171191
base_url: str = DEFAULT_API_BASE_URL,
172192
token_url: str = DEFAULT_TOKEN_URL,
193+
api_key: Optional[str] = None,
173194
verify_ssl: bool = True,
174195
retries: int = 3,
175196
timeout: Optional[float] = None,
@@ -185,6 +206,7 @@ def __init__(
185206
client_id=client_id,
186207
client_secret=client_secret,
187208
scopes=scopes,
209+
api_key=api_key,
188210
base_url=base_url,
189211
token_url=token_url,
190212
verify_ssl=verify_ssl,

0 commit comments

Comments
 (0)