brave-api-client 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brave_api/__init__.py +0 -0
- brave_api/client.py +98 -0
- brave_api/constants.py +3 -0
- brave_api/web_search/__init__.py +0 -0
- brave_api/web_search/models.py +692 -0
- brave_api_client-0.0.4.dist-info/METADATA +16 -0
- brave_api_client-0.0.4.dist-info/RECORD +9 -0
- brave_api_client-0.0.4.dist-info/WHEEL +4 -0
- brave_api_client-0.0.4.dist-info/licenses/LICENSE +21 -0
brave_api/__init__.py
ADDED
|
File without changes
|
brave_api/client.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import os
|
|
3
|
+
import typing
|
|
4
|
+
from types import TracebackType
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from brave_api.constants import BRAVE_API_KEY_ENV_VAR
|
|
8
|
+
from brave_api.web_search.models import WebSearchApiResponse, WebSearchQueryParams
|
|
9
|
+
from contextlib import AbstractAsyncContextManager
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _get_api_key_from_env() -> str | None:
|
|
14
|
+
return os.getenv(BRAVE_API_KEY_ENV_VAR)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _BraveAPIClientBase(abc.ABC):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
base_url: str | None = None,
|
|
21
|
+
api_key: str | None = None,
|
|
22
|
+
proxy: str | None = None,
|
|
23
|
+
):
|
|
24
|
+
self.base_url = base_url or "https://api.search.brave.com/res/v1"
|
|
25
|
+
self._provided_api_key = api_key
|
|
26
|
+
self._proxy = proxy
|
|
27
|
+
|
|
28
|
+
def _build_search_url(self) -> str:
|
|
29
|
+
return f"{self.base_url}/web/search"
|
|
30
|
+
|
|
31
|
+
def _build_search_params(
|
|
32
|
+
self, query_params: WebSearchQueryParams
|
|
33
|
+
) -> tuple[str, dict[str, str], dict[str, typing.Any]]:
|
|
34
|
+
url = self._build_search_url()
|
|
35
|
+
api_key = self._provided_api_key or _get_api_key_from_env()
|
|
36
|
+
if not api_key:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
"API key is required. Set it via environment variable BRAVE_API_KEY or pass it to the client."
|
|
39
|
+
)
|
|
40
|
+
headers = {
|
|
41
|
+
"X-Subscription-Token": api_key,
|
|
42
|
+
}
|
|
43
|
+
return url, headers, query_params.model_dump(exclude_unset=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class AsyncBraveAPIClient(
|
|
47
|
+
_BraveAPIClientBase, AbstractAsyncContextManager["AsyncBraveAPIClient"]
|
|
48
|
+
):
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
client: Optional[httpx.AsyncClient] = None,
|
|
52
|
+
*args: typing.Any,
|
|
53
|
+
**kwargs: typing.Any,
|
|
54
|
+
):
|
|
55
|
+
super().__init__(*args, **kwargs)
|
|
56
|
+
self._client = client if client else httpx.AsyncClient()
|
|
57
|
+
self._transport: Optional[httpx.AsyncClient] = None
|
|
58
|
+
|
|
59
|
+
async def __aenter__(self) -> "AsyncBraveAPIClient":
|
|
60
|
+
self._transport = await self._client.__aenter__()
|
|
61
|
+
return self
|
|
62
|
+
|
|
63
|
+
async def __aexit__(
|
|
64
|
+
self,
|
|
65
|
+
exc_type: type[BaseException] | None,
|
|
66
|
+
exc_value: BaseException | None,
|
|
67
|
+
traceback: TracebackType | None,
|
|
68
|
+
/,
|
|
69
|
+
) -> None:
|
|
70
|
+
self._transport = None
|
|
71
|
+
await self._client.aclose()
|
|
72
|
+
|
|
73
|
+
def _verify_transport(self) -> None:
|
|
74
|
+
if self._transport is None:
|
|
75
|
+
raise RuntimeError("Use async with `AsyncBraveAPIClient()`")
|
|
76
|
+
|
|
77
|
+
async def search(self, query: WebSearchQueryParams) -> WebSearchApiResponse:
|
|
78
|
+
self._verify_transport()
|
|
79
|
+
url, headers, query_params = self._build_search_params(query)
|
|
80
|
+
response = await self._transport.get( # type: ignore[union-attr]
|
|
81
|
+
url, headers=headers, params=query_params
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
return WebSearchApiResponse.model_validate(response.json())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class BraveAPIClient(_BraveAPIClientBase):
|
|
89
|
+
"""
|
|
90
|
+
A client for interacting with the Brave API.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def search(self, query: WebSearchQueryParams) -> WebSearchApiResponse:
|
|
94
|
+
url, headers, query_params = self._build_search_params(query)
|
|
95
|
+
response = httpx.get(url=url, headers=headers, params=query_params)
|
|
96
|
+
response.raise_for_status()
|
|
97
|
+
|
|
98
|
+
return WebSearchApiResponse.model_validate(response.json())
|
brave_api/constants.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
from typing import List, Optional, Union, Literal, Any
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
|
|
4
|
+
# Base and utility models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Thumbnail(BaseModel):
|
|
8
|
+
src: str
|
|
9
|
+
original: Optional[str] = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Profile(BaseModel):
|
|
13
|
+
name: str
|
|
14
|
+
long_name: str
|
|
15
|
+
url: Optional[str] = None
|
|
16
|
+
img: Optional[str] = None
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Rating(BaseModel):
|
|
20
|
+
ratingValue: float
|
|
21
|
+
bestRating: float
|
|
22
|
+
reviewCount: Optional[int] = None
|
|
23
|
+
profile: Optional[Profile] = None
|
|
24
|
+
is_tripadvisor: bool = True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Thing(BaseModel):
|
|
28
|
+
type: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Person(BaseModel):
|
|
32
|
+
type: str
|
|
33
|
+
email: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MetaUrl(BaseModel):
|
|
37
|
+
scheme: str
|
|
38
|
+
netloc: str
|
|
39
|
+
hostname: Optional[str] = None
|
|
40
|
+
favicon: str
|
|
41
|
+
path: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Contact(BaseModel):
|
|
45
|
+
email: Optional[str] = None
|
|
46
|
+
telephone: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ContactPoint(Thing):
|
|
50
|
+
type: Literal["contact_point"] = "contact_point"
|
|
51
|
+
telephone: Optional[str] = None
|
|
52
|
+
email: Optional[str] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DataProvider(BaseModel):
|
|
56
|
+
type: Literal["external"] = "external"
|
|
57
|
+
name: str
|
|
58
|
+
url: str
|
|
59
|
+
long_name: Optional[str] = None
|
|
60
|
+
img: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Unit(BaseModel):
|
|
64
|
+
value: float
|
|
65
|
+
units: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Answer(BaseModel):
|
|
69
|
+
text: str
|
|
70
|
+
author: Optional[str] = None
|
|
71
|
+
upvoteCount: Optional[int] = None
|
|
72
|
+
downvoteCount: Optional[int] = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class QAPage(BaseModel):
|
|
76
|
+
question: str
|
|
77
|
+
answer: Answer
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class QA(BaseModel):
|
|
81
|
+
question: str
|
|
82
|
+
answer: str
|
|
83
|
+
title: str
|
|
84
|
+
url: str
|
|
85
|
+
meta_url: Optional[MetaUrl] = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class FAQ(BaseModel):
|
|
89
|
+
type: Literal["faq"] = "faq"
|
|
90
|
+
results: List[QA]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class ForumData(BaseModel):
|
|
94
|
+
forum_name: str
|
|
95
|
+
num_answers: Optional[int] = None
|
|
96
|
+
score: Optional[str] = None
|
|
97
|
+
title: Optional[str] = None
|
|
98
|
+
question: Optional[str] = None
|
|
99
|
+
top_comment: Optional[str] = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class DiscussionResult(BaseModel):
|
|
103
|
+
type: Literal["discussion"] = "discussion"
|
|
104
|
+
data: Optional[ForumData] = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class Discussions(BaseModel):
|
|
108
|
+
type: Literal["search"] = "search"
|
|
109
|
+
results: List[DiscussionResult]
|
|
110
|
+
mutated_by_goggles: bool = False
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SearchResult(BaseModel):
|
|
114
|
+
type: Literal["search_result"] = "search_result"
|
|
115
|
+
subtype: str = "generic"
|
|
116
|
+
is_live: bool = False
|
|
117
|
+
deep_results: Optional["DeepResult"] = None
|
|
118
|
+
schemas: Optional[List[List[Any]]] = None
|
|
119
|
+
meta_url: Optional[MetaUrl] = None
|
|
120
|
+
thumbnail: Optional[Thumbnail] = None
|
|
121
|
+
age: Optional[str] = None
|
|
122
|
+
language: str
|
|
123
|
+
location: Optional["LocationResult"] = None
|
|
124
|
+
video: Optional["VideoData"] = None
|
|
125
|
+
movie: Optional["MovieData"] = None
|
|
126
|
+
faq: Optional[FAQ] = None
|
|
127
|
+
qa: Optional[QAPage] = None
|
|
128
|
+
book: Optional["Book"] = None
|
|
129
|
+
rating: Optional[Rating] = None
|
|
130
|
+
article: Optional["Article"] = None
|
|
131
|
+
product: Optional[Union["Product", "Review"]] = None
|
|
132
|
+
product_cluster: Optional[List[Union["Product", "Review"]]] = None
|
|
133
|
+
cluster_type: Optional[str] = None
|
|
134
|
+
cluster: Optional[List["Result"]] = None
|
|
135
|
+
creative_work: Optional["CreativeWork"] = None
|
|
136
|
+
music_recording: Optional["MusicRecording"] = None
|
|
137
|
+
review: Optional["Review"] = None
|
|
138
|
+
software: Optional["Software"] = None
|
|
139
|
+
recipe: Optional["Recipe"] = None
|
|
140
|
+
organization: Optional["Organization"] = None
|
|
141
|
+
content_type: Optional[str] = None
|
|
142
|
+
extra_snippets: Optional[List[str]] = None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class Result(BaseModel):
|
|
146
|
+
title: str
|
|
147
|
+
url: str
|
|
148
|
+
is_source_local: bool = True
|
|
149
|
+
is_source_both: bool = True
|
|
150
|
+
description: Optional[str] = None
|
|
151
|
+
page_age: Optional[str] = None
|
|
152
|
+
page_fetched: Optional[str] = None
|
|
153
|
+
profile: Optional[Profile] = None
|
|
154
|
+
language: Optional[str] = None
|
|
155
|
+
family_friendly: bool = True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class LocationWebResult(Result):
|
|
159
|
+
meta_url: MetaUrl
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class LocationResult(Result):
|
|
163
|
+
type: Literal["location_result"] = "location_result"
|
|
164
|
+
id: Optional[str] = None
|
|
165
|
+
provider_url: str
|
|
166
|
+
coordinates: Optional[List[float]] = None
|
|
167
|
+
zoom_level: int
|
|
168
|
+
thumbnail: Optional[Thumbnail] = None
|
|
169
|
+
postal_address: Optional["PostalAddress"] = None
|
|
170
|
+
opening_hours: Optional["OpeningHours"] = None
|
|
171
|
+
contact: Optional[Contact] = None
|
|
172
|
+
price_range: Optional[str] = None
|
|
173
|
+
rating: Optional[Rating] = None
|
|
174
|
+
distance: Optional[Unit] = None
|
|
175
|
+
profiles: Optional[List[DataProvider]] = None
|
|
176
|
+
reviews: Optional["Reviews"] = None
|
|
177
|
+
pictures: Optional["PictureResults"] = None
|
|
178
|
+
action: Optional["Action"] = None
|
|
179
|
+
serves_cuisine: Optional[List[str]] = None
|
|
180
|
+
categories: Optional[List[str]] = None
|
|
181
|
+
icon_category: Optional[str] = None
|
|
182
|
+
results: Optional[LocationWebResult] = None
|
|
183
|
+
timezone: Optional[str] = None
|
|
184
|
+
timezone_offset: Optional[str] = None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class Locations(BaseModel):
|
|
188
|
+
type: Literal["locations"] = "locations"
|
|
189
|
+
results: List[LocationResult]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Summarizer(BaseModel):
|
|
193
|
+
type: Literal["summarizer"] = "summarizer"
|
|
194
|
+
key: str
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class RichCallbackHint(BaseModel):
|
|
198
|
+
vertical: str
|
|
199
|
+
callback_key: str
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class RichCallbackInfo(BaseModel):
|
|
203
|
+
type: Literal["rich"] = "rich"
|
|
204
|
+
hint: Optional[RichCallbackHint] = None
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class ResultReference(BaseModel):
|
|
208
|
+
type: str
|
|
209
|
+
index: Optional[int] = None
|
|
210
|
+
all: bool
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class MixedResponse(BaseModel):
|
|
214
|
+
type: Literal["mixed"] = "mixed"
|
|
215
|
+
main: Optional[List[ResultReference]] = None
|
|
216
|
+
top: Optional[List[ResultReference]] = None
|
|
217
|
+
side: Optional[List[ResultReference]] = None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class LocalPoiSearchApiResponse(BaseModel):
|
|
221
|
+
type: Literal["local_pois"] = "local_pois"
|
|
222
|
+
results: Optional[List[LocationResult]] = None
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class LocalDescriptionsSearchApiResponse(BaseModel):
|
|
226
|
+
type: Literal["local_descriptions"] = "local_descriptions"
|
|
227
|
+
results: Optional[List["LocationDescription"]] = None
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class LocationDescription(BaseModel):
|
|
231
|
+
type: Literal["local_description"] = "local_description"
|
|
232
|
+
id: str
|
|
233
|
+
description: Optional[str] = None
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class WebSearchApiResponse(BaseModel):
|
|
237
|
+
type: Literal["search"]
|
|
238
|
+
discussions: Optional[Discussions] = None
|
|
239
|
+
faq: Optional[FAQ] = None
|
|
240
|
+
infobox: Optional["GraphInfobox"] = None
|
|
241
|
+
locations: Optional[Locations] = None
|
|
242
|
+
mixed: Optional[MixedResponse] = None
|
|
243
|
+
news: Optional["News"] = None
|
|
244
|
+
query: Optional["Query"] = None
|
|
245
|
+
videos: Optional["Videos"] = None
|
|
246
|
+
web: Optional["Search"] = None
|
|
247
|
+
summarizer: Optional[Summarizer] = None
|
|
248
|
+
rich: Optional[RichCallbackInfo] = None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class Search(BaseModel):
|
|
252
|
+
type: Literal["search"] = "search"
|
|
253
|
+
results: List[SearchResult]
|
|
254
|
+
family_friendly: bool
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class Query(BaseModel):
|
|
258
|
+
original: str
|
|
259
|
+
show_strict_warning: Optional[bool] = None
|
|
260
|
+
altered: Optional[str] = None
|
|
261
|
+
safesearch: Optional[bool] = None
|
|
262
|
+
is_navigational: Optional[bool] = None
|
|
263
|
+
is_geolocal: Optional[bool] = None
|
|
264
|
+
local_decision: Optional[str] = None
|
|
265
|
+
local_locations_idx: Optional[int] = None
|
|
266
|
+
is_trending: Optional[bool] = None
|
|
267
|
+
is_news_breaking: Optional[bool] = None
|
|
268
|
+
ask_for_location: Optional[bool] = None
|
|
269
|
+
language: Optional["Language"] = None
|
|
270
|
+
spellcheck_off: Optional[bool] = None
|
|
271
|
+
country: Optional[str] = None
|
|
272
|
+
bad_results: Optional[bool] = None
|
|
273
|
+
should_fallback: Optional[bool] = None
|
|
274
|
+
lat: Optional[str] = None
|
|
275
|
+
long: Optional[str] = None
|
|
276
|
+
postal_code: Optional[str] = None
|
|
277
|
+
city: Optional[str] = None
|
|
278
|
+
state: Optional[str] = None
|
|
279
|
+
header_country: Optional[str] = None
|
|
280
|
+
more_results_available: Optional[bool] = None
|
|
281
|
+
custom_location_label: Optional[str] = None
|
|
282
|
+
reddit_cluster: Optional[str] = None
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class Language(BaseModel):
|
|
286
|
+
main: str
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class NewsResult(Result):
|
|
290
|
+
meta_url: Optional[MetaUrl] = None
|
|
291
|
+
source: Optional[str] = None
|
|
292
|
+
breaking: bool
|
|
293
|
+
is_live: bool
|
|
294
|
+
thumbnail: Optional[Thumbnail] = None
|
|
295
|
+
age: Optional[str] = None
|
|
296
|
+
extra_snippets: Optional[List[str]] = None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class News(BaseModel):
|
|
300
|
+
type: Literal["news"] = "news"
|
|
301
|
+
results: List[NewsResult]
|
|
302
|
+
mutated_by_goggles: bool = False
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
class VideoData(BaseModel):
|
|
306
|
+
duration: Optional[str] = None
|
|
307
|
+
views: Optional[str] = None
|
|
308
|
+
creator: Optional[str] = None
|
|
309
|
+
publisher: Optional[str] = None
|
|
310
|
+
thumbnail: Optional[Thumbnail] = None
|
|
311
|
+
tags: Optional[List[str]] = None
|
|
312
|
+
author: Optional[Profile] = None
|
|
313
|
+
requires_subscription: Optional[bool] = None
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class VideoResult(Result):
|
|
317
|
+
type: Literal["video_result"] = "video_result"
|
|
318
|
+
video: VideoData
|
|
319
|
+
meta_url: Optional[MetaUrl] = None
|
|
320
|
+
thumbnail: Optional[Thumbnail] = None
|
|
321
|
+
age: Optional[str] = None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class Videos(BaseModel):
|
|
325
|
+
type: Literal["videos"] = "videos"
|
|
326
|
+
results: List[VideoResult]
|
|
327
|
+
mutated_by_goggles: Optional[bool] = False
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
class DeepResult(BaseModel):
|
|
331
|
+
news: Optional[List[NewsResult]] = None
|
|
332
|
+
buttons: Optional[List["ButtonResult"]] = None
|
|
333
|
+
videos: Optional[List[VideoResult]] = None
|
|
334
|
+
images: Optional[List["Image"]] = None
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class ButtonResult(BaseModel):
|
|
338
|
+
type: Literal["button_result"] = "button_result"
|
|
339
|
+
title: str
|
|
340
|
+
url: str
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class ImageProperties(BaseModel):
|
|
344
|
+
url: str
|
|
345
|
+
resized: str
|
|
346
|
+
placeholder: str
|
|
347
|
+
height: Optional[int] = None
|
|
348
|
+
width: Optional[int] = None
|
|
349
|
+
format: Optional[str] = None
|
|
350
|
+
content_size: Optional[str] = None
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class Image(BaseModel):
|
|
354
|
+
thumbnail: Thumbnail
|
|
355
|
+
url: Optional[str] = None
|
|
356
|
+
properties: Optional[ImageProperties] = None
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class Review(BaseModel):
|
|
360
|
+
type: Literal["review"] = "review"
|
|
361
|
+
name: str
|
|
362
|
+
thumbnail: Thumbnail
|
|
363
|
+
description: str
|
|
364
|
+
rating: Rating
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class Product(BaseModel):
|
|
368
|
+
type: Literal["product"] = "product"
|
|
369
|
+
name: str
|
|
370
|
+
category: Optional[str] = None
|
|
371
|
+
price: str
|
|
372
|
+
thumbnail: Thumbnail
|
|
373
|
+
description: Optional[str] = None
|
|
374
|
+
offers: Optional[List["Offer"]] = None
|
|
375
|
+
rating: Optional[Rating] = None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class Offer(BaseModel):
|
|
379
|
+
url: str
|
|
380
|
+
priceCurrency: str
|
|
381
|
+
price: str
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
class Book(BaseModel):
|
|
385
|
+
title: str
|
|
386
|
+
author: List[Person]
|
|
387
|
+
date: Optional[str] = None
|
|
388
|
+
price: Optional["Price"] = None
|
|
389
|
+
pages: Optional[int] = None
|
|
390
|
+
publisher: Optional[Person] = None
|
|
391
|
+
rating: Optional[Rating] = None
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class Price(BaseModel):
|
|
395
|
+
price: str
|
|
396
|
+
price_currency: str
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class Article(BaseModel):
|
|
400
|
+
author: Optional[List[Person]] = None
|
|
401
|
+
date: Optional[str] = None
|
|
402
|
+
publisher: Optional["Organization"] = None
|
|
403
|
+
thumbnail: Optional[Thumbnail] = None
|
|
404
|
+
isAccessibleForFree: Optional[bool] = None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class Organization(BaseModel):
|
|
408
|
+
type: Literal["organization"] = "organization"
|
|
409
|
+
contact_points: Optional[List[ContactPoint]] = None
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class Software(BaseModel):
|
|
413
|
+
name: Optional[str] = None
|
|
414
|
+
author: Optional[str] = None
|
|
415
|
+
version: Optional[str] = None
|
|
416
|
+
codeRepository: Optional[str] = None
|
|
417
|
+
homepage: Optional[str] = None
|
|
418
|
+
datePublisher: Optional[str] = None
|
|
419
|
+
is_npm: Optional[bool] = None
|
|
420
|
+
is_pypi: Optional[bool] = None
|
|
421
|
+
stars: Optional[int] = None
|
|
422
|
+
forks: Optional[int] = None
|
|
423
|
+
ProgrammingLanguage: Optional[str] = None
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class MusicRecording(BaseModel):
|
|
427
|
+
name: str
|
|
428
|
+
thumbnail: Optional[Thumbnail] = None
|
|
429
|
+
rating: Optional[Rating] = None
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class MovieData(BaseModel):
|
|
433
|
+
name: Optional[str] = None
|
|
434
|
+
description: Optional[str] = None
|
|
435
|
+
url: Optional[str] = None
|
|
436
|
+
thumbnail: Optional[Thumbnail] = None
|
|
437
|
+
release: Optional[str] = None
|
|
438
|
+
directors: Optional[List[Person]] = None
|
|
439
|
+
actors: Optional[List[Person]] = None
|
|
440
|
+
rating: Optional[Rating] = None
|
|
441
|
+
duration: Optional[str] = None
|
|
442
|
+
genre: Optional[List[str]] = None
|
|
443
|
+
query: Optional[str] = None
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
class PostalAddress(BaseModel):
|
|
447
|
+
type: Literal["PostalAddress"] = "PostalAddress"
|
|
448
|
+
country: Optional[str] = None
|
|
449
|
+
postalCode: Optional[str] = None
|
|
450
|
+
streetAddress: Optional[str] = None
|
|
451
|
+
addressRegion: Optional[str] = None
|
|
452
|
+
addressLocality: Optional[str] = None
|
|
453
|
+
displayAddress: str
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
class DayOpeningHours(BaseModel):
|
|
457
|
+
abbr_name: str
|
|
458
|
+
full_name: str
|
|
459
|
+
opens: str
|
|
460
|
+
closes: str
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class OpeningHours(BaseModel):
|
|
464
|
+
current_day: Optional[List[DayOpeningHours]] = None
|
|
465
|
+
days: Optional[List[List[DayOpeningHours]]] = None
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class Reviews(BaseModel):
|
|
469
|
+
results: List["TripAdvisorReview"] # TripAdvisorReview
|
|
470
|
+
viewMoreUrl: str
|
|
471
|
+
reviews_in_foreign_language: bool
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class TripAdvisorReview(BaseModel):
|
|
475
|
+
title: str
|
|
476
|
+
description: str
|
|
477
|
+
date: str
|
|
478
|
+
rating: Rating
|
|
479
|
+
author: Person
|
|
480
|
+
review_url: str
|
|
481
|
+
language: str
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
class Action(BaseModel):
|
|
485
|
+
type: str
|
|
486
|
+
url: str
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
# Infobox / Graph section
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
class AbstractGraphInfobox(Result):
|
|
493
|
+
type: str = "infobox"
|
|
494
|
+
position: int
|
|
495
|
+
label: Optional[str] = None
|
|
496
|
+
category: Optional[str] = None
|
|
497
|
+
long_desc: Optional[str] = None
|
|
498
|
+
thumbnail: Optional[Thumbnail] = None
|
|
499
|
+
attributes: Optional[List[List[str]]] = None
|
|
500
|
+
profiles: Optional[Union[List[Profile], List[DataProvider]]] = None
|
|
501
|
+
website_url: Optional[str] = None
|
|
502
|
+
ratings: Optional[List[Rating]] = None
|
|
503
|
+
providers: Optional[List[DataProvider]] = None
|
|
504
|
+
distance: Optional[Unit] = None
|
|
505
|
+
images: Optional[List[Thumbnail]] = None
|
|
506
|
+
movie: Optional[MovieData] = None
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
class GenericInfobox(AbstractGraphInfobox):
|
|
510
|
+
subtype: str = "generic"
|
|
511
|
+
found_in_urls: Optional[List[str]] = None
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
class EntityInfobox(AbstractGraphInfobox):
|
|
515
|
+
subtype: Literal["entity"] = "entity"
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class QAInfobox(AbstractGraphInfobox):
|
|
519
|
+
subtype: Literal["code"] = "code"
|
|
520
|
+
data: QAPage
|
|
521
|
+
meta_url: Optional[MetaUrl] = None
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
class InfoboxWithLocation(AbstractGraphInfobox):
|
|
525
|
+
subtype: Literal["location"] = "location"
|
|
526
|
+
is_location: bool
|
|
527
|
+
coordinates: Optional[List[float]] = None
|
|
528
|
+
zoom_level: int
|
|
529
|
+
location: Optional[LocationResult] = None
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
class InfoboxPlace(AbstractGraphInfobox):
|
|
533
|
+
subtype: Literal["place"] = "place"
|
|
534
|
+
location: LocationResult
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
class GraphInfobox(BaseModel):
|
|
538
|
+
type: Literal["graph"] = "graph"
|
|
539
|
+
results: List[
|
|
540
|
+
Union[
|
|
541
|
+
GenericInfobox,
|
|
542
|
+
QAInfobox,
|
|
543
|
+
InfoboxPlace,
|
|
544
|
+
InfoboxWithLocation,
|
|
545
|
+
EntityInfobox,
|
|
546
|
+
]
|
|
547
|
+
]
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class PictureResults(BaseModel):
|
|
551
|
+
viewMoreUrl: Optional[str] = None
|
|
552
|
+
results: List[Thumbnail]
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
class Recipe(BaseModel):
|
|
556
|
+
title: str
|
|
557
|
+
description: str
|
|
558
|
+
thumbnail: Thumbnail
|
|
559
|
+
url: str
|
|
560
|
+
domain: str
|
|
561
|
+
favicon: str
|
|
562
|
+
time: Optional[str] = None
|
|
563
|
+
prep_time: Optional[str] = None
|
|
564
|
+
cook_time: Optional[str] = None
|
|
565
|
+
ingredients: Optional[str] = None
|
|
566
|
+
instructions: Optional[List["HowTo"]] = None
|
|
567
|
+
servings: Optional[int] = None
|
|
568
|
+
calories: Optional[int] = None
|
|
569
|
+
rating: Optional[Rating] = None
|
|
570
|
+
recipeCategory: Optional[str] = None
|
|
571
|
+
recipeCuisine: Optional[str] = None
|
|
572
|
+
video: Optional[VideoData] = None
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class HowTo(BaseModel):
|
|
576
|
+
text: str
|
|
577
|
+
name: Optional[str] = None
|
|
578
|
+
url: Optional[str] = None
|
|
579
|
+
image: Optional[List[str]] = None
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
class CreativeWork(BaseModel):
|
|
583
|
+
name: str
|
|
584
|
+
thumbnail: Thumbnail
|
|
585
|
+
rating: Optional[Rating] = None
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# Resolve forward references for all relevant models
|
|
589
|
+
SearchResult.model_rebuild()
|
|
590
|
+
DeepResult.model_rebuild()
|
|
591
|
+
LocationResult.model_rebuild()
|
|
592
|
+
Product.model_rebuild()
|
|
593
|
+
Book.model_rebuild()
|
|
594
|
+
Article.model_rebuild()
|
|
595
|
+
Reviews.model_rebuild()
|
|
596
|
+
TripAdvisorReview.model_rebuild()
|
|
597
|
+
WebSearchApiResponse.model_rebuild()
|
|
598
|
+
LocalDescriptionsSearchApiResponse.model_rebuild()
|
|
599
|
+
Recipe.model_rebuild()
|
|
600
|
+
HowTo.model_rebuild()
|
|
601
|
+
CreativeWork.model_rebuild()
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
class WebSearchQueryParams(BaseModel):
|
|
605
|
+
q: str = Field(
|
|
606
|
+
...,
|
|
607
|
+
description="The user’s search query term. Can not be empty. Max 400 chars and 50 words.",
|
|
608
|
+
)
|
|
609
|
+
country: Optional[str] = Field(
|
|
610
|
+
"US",
|
|
611
|
+
max_length=2,
|
|
612
|
+
description="2 character country code. See country codes list.",
|
|
613
|
+
)
|
|
614
|
+
search_lang: Optional[str] = Field(
|
|
615
|
+
"en",
|
|
616
|
+
min_length=2,
|
|
617
|
+
description="2+ character language code. See language codes list.",
|
|
618
|
+
)
|
|
619
|
+
ui_lang: Optional[str] = Field(
|
|
620
|
+
"en-US",
|
|
621
|
+
description="Format <language_code>-<country_code> (see RFC9110 and UI language codes list).",
|
|
622
|
+
)
|
|
623
|
+
count: Optional[int] = Field(
|
|
624
|
+
20, ge=1, le=20, description="Number of search results to return (max 20)."
|
|
625
|
+
)
|
|
626
|
+
offset: Optional[int] = Field(
|
|
627
|
+
0, ge=0, le=9, description="Zero-based page offset (max 9)."
|
|
628
|
+
)
|
|
629
|
+
safesearch: Optional[str] = Field(
|
|
630
|
+
"moderate",
|
|
631
|
+
pattern="^(off|moderate|strict)$",
|
|
632
|
+
description="Adult content filter: off, moderate, strict.",
|
|
633
|
+
)
|
|
634
|
+
freshness: Optional[str] = Field(
|
|
635
|
+
None,
|
|
636
|
+
description=(
|
|
637
|
+
"Limits discovery to a time window. One of: pd (24h), pw (7d), pm (31d), py (365d), "
|
|
638
|
+
"or range YYYY-MM-DDtoYYYY-MM-DD."
|
|
639
|
+
),
|
|
640
|
+
)
|
|
641
|
+
text_decorations: Optional[bool] = Field(
|
|
642
|
+
True,
|
|
643
|
+
description="Whether display strings include decoration markers (highlighting).",
|
|
644
|
+
)
|
|
645
|
+
spellcheck: Optional[bool] = Field(True, description="Spellcheck provided query.")
|
|
646
|
+
result_filter: Optional[str] = Field(
|
|
647
|
+
None,
|
|
648
|
+
description=(
|
|
649
|
+
"Comma delimited result types to include. E.g. discussions,faq,news,web,infobox,query,summarizer,videos,locations."
|
|
650
|
+
),
|
|
651
|
+
)
|
|
652
|
+
goggles_id: Optional[str] = Field(
|
|
653
|
+
None,
|
|
654
|
+
description="(Deprecated) Goggle for custom re-ranking (use `goggles` instead).",
|
|
655
|
+
)
|
|
656
|
+
goggles: Optional[List[str]] = Field(
|
|
657
|
+
None, description="List of goggle URLs/definitions for custom re-ranking."
|
|
658
|
+
)
|
|
659
|
+
units: Optional[str] = Field(
|
|
660
|
+
None,
|
|
661
|
+
pattern="^(metric|imperial)$",
|
|
662
|
+
description="Measurement units: metric, imperial.",
|
|
663
|
+
)
|
|
664
|
+
extra_snippets: Optional[bool] = Field(
|
|
665
|
+
None, description="Get up to 5 additional, alternative result snippets."
|
|
666
|
+
)
|
|
667
|
+
summary: Optional[bool] = Field(
|
|
668
|
+
None, description="Enable summary key generation in web search results."
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
class LocalSearchQueryParams(BaseModel):
|
|
673
|
+
ids: List[str] = Field(
|
|
674
|
+
...,
|
|
675
|
+
min_length=1,
|
|
676
|
+
max_length=20,
|
|
677
|
+
description="List of 1 to 20 non-empty unique location IDs.",
|
|
678
|
+
)
|
|
679
|
+
search_lang: Optional[str] = Field(
|
|
680
|
+
"en",
|
|
681
|
+
min_length=2,
|
|
682
|
+
description="2+ character language code. See language codes list.",
|
|
683
|
+
)
|
|
684
|
+
ui_lang: Optional[str] = Field(
|
|
685
|
+
"en-US",
|
|
686
|
+
description="Format <language_code>-<country_code> (see RFC9110 and UI language codes list).",
|
|
687
|
+
)
|
|
688
|
+
units: Optional[str] = Field(
|
|
689
|
+
None,
|
|
690
|
+
pattern="^(metric|imperial)$",
|
|
691
|
+
description="Measurement units: metric, imperial.",
|
|
692
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brave-api-client
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Brave API client for Python
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Typing :: Typed
|
|
11
|
+
Requires-Python: <4,>=3.10
|
|
12
|
+
Requires-Dist: httpx>=0.28.1
|
|
13
|
+
Requires-Dist: pydantic>=2.11.5
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# brave-api
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
brave_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
brave_api/client.py,sha256=SoWxSxftPNN3KaIY7SpTvqocjANVek6-o6vz5yO4N_8,3209
|
|
3
|
+
brave_api/constants.py,sha256=_b6tIRQ1xpl4n0n20bg664Ssgv-o6vUAHg36W4mYBu8,78
|
|
4
|
+
brave_api/web_search/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
brave_api/web_search/models.py,sha256=azVjtLGn-wUyeJvePwfy9tQpyVaoI5UZiEpOwkeg9-4,18394
|
|
6
|
+
brave_api_client-0.0.4.dist-info/METADATA,sha256=lO4oZ13jpSpn7HqIifn1yIMOzOog2ysEH1cRof4nY-A,492
|
|
7
|
+
brave_api_client-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
8
|
+
brave_api_client-0.0.4.dist-info/licenses/LICENSE,sha256=z59681Nh2eCveE-2Ch7Yvlu5OSM_vXBnFXWpDkWNsA4,1061
|
|
9
|
+
brave_api_client-0.0.4.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|