openserp 0.1.0__tar.gz
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.
- openserp-0.1.0/.gitignore +8 -0
- openserp-0.1.0/PKG-INFO +213 -0
- openserp-0.1.0/README.md +178 -0
- openserp-0.1.0/pyproject.toml +77 -0
- openserp-0.1.0/src/openserp/__init__.py +66 -0
- openserp-0.1.0/src/openserp/backend.py +42 -0
- openserp-0.1.0/src/openserp/client.py +534 -0
- openserp-0.1.0/src/openserp/errors.py +83 -0
- openserp-0.1.0/src/openserp/models.py +318 -0
- openserp-0.1.0/src/openserp/py.typed +1 -0
- openserp-0.1.0/tests/test_client.py +297 -0
openserp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openserp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the OpenSERP self-hosted server and OpenSERP Cloud.
|
|
5
|
+
Project-URL: Homepage, https://openserp.org
|
|
6
|
+
Project-URL: Documentation, https://openserp.org/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/karust/openserp
|
|
8
|
+
Project-URL: Issues, https://github.com/karust/openserp/issues
|
|
9
|
+
Author: OpenSERP
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
Keywords: ai-grounding,baidu,bing,duckduckgo,ecosia,google,openserp,search,seo,serp,yandex
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx<1,>=0.27
|
|
24
|
+
Requires-Dist: pydantic<3,>=2.7
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
32
|
+
Provides-Extra: pandas
|
|
33
|
+
Requires-Dist: pandas>=2.0; extra == 'pandas'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# OpenSERP Python SDK
|
|
37
|
+
|
|
38
|
+
Alpha: API may change before 1.0.
|
|
39
|
+
|
|
40
|
+
Python SDK for the OpenSERP self-hosted server and OpenSERP Cloud. The same client works against both backends.
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install openserp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
For DataFrame export:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install "openserp[pandas]"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## OSS Mode
|
|
55
|
+
|
|
56
|
+
OSS mode is the default. Run the open source server locally, then point the SDK at it:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from openserp import OpenSERP
|
|
60
|
+
|
|
61
|
+
client = OpenSERP(base_url="http://localhost:7000")
|
|
62
|
+
|
|
63
|
+
resp = client.search(
|
|
64
|
+
engine="google",
|
|
65
|
+
text="openserp",
|
|
66
|
+
limit=10,
|
|
67
|
+
region="US",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
print(resp.results[0].title, resp.results[0].url)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If you omit every option, the client uses `http://localhost:7000`.
|
|
74
|
+
|
|
75
|
+
## Cloud Mode
|
|
76
|
+
|
|
77
|
+
Pass an API key and the client defaults to `https://api.openserp.org/v1`.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import os
|
|
81
|
+
|
|
82
|
+
from openserp import OpenSERP
|
|
83
|
+
|
|
84
|
+
client = OpenSERP(api_key=os.environ["OPENSERP_API_KEY"])
|
|
85
|
+
resp = client.search(engine="google", text="openserp")
|
|
86
|
+
|
|
87
|
+
print(resp.results[0].title)
|
|
88
|
+
print(client.last_response.credits)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Async
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
import asyncio
|
|
95
|
+
import os
|
|
96
|
+
|
|
97
|
+
from openserp import AsyncOpenSERP
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def main() -> None:
|
|
101
|
+
async with AsyncOpenSERP(api_key=os.environ["OPENSERP_API_KEY"]) as client:
|
|
102
|
+
resp = await client.search(engine="google", text="openserp")
|
|
103
|
+
print(resp.results[0].title)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
asyncio.run(main())
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Mega Search
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from openserp import OpenSERP
|
|
113
|
+
|
|
114
|
+
client = OpenSERP()
|
|
115
|
+
|
|
116
|
+
mega = client.mega_search(
|
|
117
|
+
text="openserp",
|
|
118
|
+
engines=["google", "bing", "yandex"],
|
|
119
|
+
mode="balanced",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
df = mega.to_pandas()
|
|
123
|
+
print(df[["rank", "title", "url", "engine"]])
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Convenience helpers are also available:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
client.fast_search(text="openserp", engines=["google", "bing"])
|
|
130
|
+
client.any_search(text="openserp", engines=["google", "bing"])
|
|
131
|
+
client.mega_image(text="golang logo", engines=["google", "bing"])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## AI / RAG
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from openserp import OpenSERP
|
|
138
|
+
|
|
139
|
+
client = OpenSERP()
|
|
140
|
+
resp = client.search(engine="google", text="latest postgres indexing guide", limit=5)
|
|
141
|
+
|
|
142
|
+
context = "\n\n".join(
|
|
143
|
+
f"{item.title}\n{item.url}\n{item.snippet}"
|
|
144
|
+
for item in resp.results
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
prompt = f"Use these web results as grounding:\n\n{context}\n\nSummarize the key points."
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## SEO Keyword Tracker
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from openserp import OpenSERP
|
|
154
|
+
|
|
155
|
+
client = OpenSERP()
|
|
156
|
+
keywords = ["openserp", "serp api", "google search api"]
|
|
157
|
+
frames = []
|
|
158
|
+
|
|
159
|
+
for keyword in keywords:
|
|
160
|
+
resp = client.search(engine="google", text=keyword, region="US", limit=10)
|
|
161
|
+
frame = resp.to_pandas()
|
|
162
|
+
frame["keyword"] = keyword
|
|
163
|
+
frames.append(frame)
|
|
164
|
+
|
|
165
|
+
report = __import__("pandas").concat(frames, ignore_index=True)
|
|
166
|
+
report.to_csv("rank-report.csv", index=False)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Async Batch
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import asyncio
|
|
173
|
+
|
|
174
|
+
from openserp import AsyncOpenSERP
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def main() -> None:
|
|
178
|
+
sem = asyncio.Semaphore(20)
|
|
179
|
+
queries = [f"keyword {i}" for i in range(500)]
|
|
180
|
+
|
|
181
|
+
async with AsyncOpenSERP() as client:
|
|
182
|
+
async def run(query: str):
|
|
183
|
+
async with sem:
|
|
184
|
+
return await client.search(engine="google", text=query, limit=10)
|
|
185
|
+
|
|
186
|
+
responses = await asyncio.gather(*(run(query) for query in queries))
|
|
187
|
+
print(len(responses))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
asyncio.run(main())
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Endpoint Availability
|
|
194
|
+
|
|
195
|
+
Search endpoints work in both modes. Operational OSS endpoints such as `health()`, `stats()`, `parse_google()`, and `parse_bing()` require a self-hosted server and raise `OssOnlyError` in Cloud mode.
|
|
196
|
+
|
|
197
|
+
Cloud account endpoints such as `me()`, `pricing()`, `engines_status()`, and `engines_capabilities()` require Cloud mode and raise `CloudOnlyError` in OSS mode.
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
python -m pip install -e ".[dev,pandas]"
|
|
203
|
+
pytest
|
|
204
|
+
ruff check .
|
|
205
|
+
mypy src
|
|
206
|
+
python -m build
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The project is scaffolded for `uv` too:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
uv build
|
|
213
|
+
```
|
openserp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# OpenSERP Python SDK
|
|
2
|
+
|
|
3
|
+
Alpha: API may change before 1.0.
|
|
4
|
+
|
|
5
|
+
Python SDK for the OpenSERP self-hosted server and OpenSERP Cloud. The same client works against both backends.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install openserp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
For DataFrame export:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install "openserp[pandas]"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## OSS Mode
|
|
20
|
+
|
|
21
|
+
OSS mode is the default. Run the open source server locally, then point the SDK at it:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from openserp import OpenSERP
|
|
25
|
+
|
|
26
|
+
client = OpenSERP(base_url="http://localhost:7000")
|
|
27
|
+
|
|
28
|
+
resp = client.search(
|
|
29
|
+
engine="google",
|
|
30
|
+
text="openserp",
|
|
31
|
+
limit=10,
|
|
32
|
+
region="US",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
print(resp.results[0].title, resp.results[0].url)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
If you omit every option, the client uses `http://localhost:7000`.
|
|
39
|
+
|
|
40
|
+
## Cloud Mode
|
|
41
|
+
|
|
42
|
+
Pass an API key and the client defaults to `https://api.openserp.org/v1`.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import os
|
|
46
|
+
|
|
47
|
+
from openserp import OpenSERP
|
|
48
|
+
|
|
49
|
+
client = OpenSERP(api_key=os.environ["OPENSERP_API_KEY"])
|
|
50
|
+
resp = client.search(engine="google", text="openserp")
|
|
51
|
+
|
|
52
|
+
print(resp.results[0].title)
|
|
53
|
+
print(client.last_response.credits)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Async
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import asyncio
|
|
60
|
+
import os
|
|
61
|
+
|
|
62
|
+
from openserp import AsyncOpenSERP
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def main() -> None:
|
|
66
|
+
async with AsyncOpenSERP(api_key=os.environ["OPENSERP_API_KEY"]) as client:
|
|
67
|
+
resp = await client.search(engine="google", text="openserp")
|
|
68
|
+
print(resp.results[0].title)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
asyncio.run(main())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Mega Search
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from openserp import OpenSERP
|
|
78
|
+
|
|
79
|
+
client = OpenSERP()
|
|
80
|
+
|
|
81
|
+
mega = client.mega_search(
|
|
82
|
+
text="openserp",
|
|
83
|
+
engines=["google", "bing", "yandex"],
|
|
84
|
+
mode="balanced",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
df = mega.to_pandas()
|
|
88
|
+
print(df[["rank", "title", "url", "engine"]])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Convenience helpers are also available:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
client.fast_search(text="openserp", engines=["google", "bing"])
|
|
95
|
+
client.any_search(text="openserp", engines=["google", "bing"])
|
|
96
|
+
client.mega_image(text="golang logo", engines=["google", "bing"])
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## AI / RAG
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from openserp import OpenSERP
|
|
103
|
+
|
|
104
|
+
client = OpenSERP()
|
|
105
|
+
resp = client.search(engine="google", text="latest postgres indexing guide", limit=5)
|
|
106
|
+
|
|
107
|
+
context = "\n\n".join(
|
|
108
|
+
f"{item.title}\n{item.url}\n{item.snippet}"
|
|
109
|
+
for item in resp.results
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
prompt = f"Use these web results as grounding:\n\n{context}\n\nSummarize the key points."
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## SEO Keyword Tracker
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from openserp import OpenSERP
|
|
119
|
+
|
|
120
|
+
client = OpenSERP()
|
|
121
|
+
keywords = ["openserp", "serp api", "google search api"]
|
|
122
|
+
frames = []
|
|
123
|
+
|
|
124
|
+
for keyword in keywords:
|
|
125
|
+
resp = client.search(engine="google", text=keyword, region="US", limit=10)
|
|
126
|
+
frame = resp.to_pandas()
|
|
127
|
+
frame["keyword"] = keyword
|
|
128
|
+
frames.append(frame)
|
|
129
|
+
|
|
130
|
+
report = __import__("pandas").concat(frames, ignore_index=True)
|
|
131
|
+
report.to_csv("rank-report.csv", index=False)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Async Batch
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import asyncio
|
|
138
|
+
|
|
139
|
+
from openserp import AsyncOpenSERP
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def main() -> None:
|
|
143
|
+
sem = asyncio.Semaphore(20)
|
|
144
|
+
queries = [f"keyword {i}" for i in range(500)]
|
|
145
|
+
|
|
146
|
+
async with AsyncOpenSERP() as client:
|
|
147
|
+
async def run(query: str):
|
|
148
|
+
async with sem:
|
|
149
|
+
return await client.search(engine="google", text=query, limit=10)
|
|
150
|
+
|
|
151
|
+
responses = await asyncio.gather(*(run(query) for query in queries))
|
|
152
|
+
print(len(responses))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
asyncio.run(main())
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Endpoint Availability
|
|
159
|
+
|
|
160
|
+
Search endpoints work in both modes. Operational OSS endpoints such as `health()`, `stats()`, `parse_google()`, and `parse_bing()` require a self-hosted server and raise `OssOnlyError` in Cloud mode.
|
|
161
|
+
|
|
162
|
+
Cloud account endpoints such as `me()`, `pricing()`, `engines_status()`, and `engines_capabilities()` require Cloud mode and raise `CloudOnlyError` in OSS mode.
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
python -m pip install -e ".[dev,pandas]"
|
|
168
|
+
pytest
|
|
169
|
+
ruff check .
|
|
170
|
+
mypy src
|
|
171
|
+
python -m build
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The project is scaffolded for `uv` too:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
uv build
|
|
178
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openserp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the OpenSERP self-hosted server and OpenSERP Cloud."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [{ name = "OpenSERP" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"openserp",
|
|
15
|
+
"serp",
|
|
16
|
+
"search",
|
|
17
|
+
"google",
|
|
18
|
+
"bing",
|
|
19
|
+
"yandex",
|
|
20
|
+
"baidu",
|
|
21
|
+
"duckduckgo",
|
|
22
|
+
"ecosia",
|
|
23
|
+
"seo",
|
|
24
|
+
"ai-grounding",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 3 - Alpha",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Intended Audience :: Science/Research",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Typing :: Typed",
|
|
37
|
+
]
|
|
38
|
+
dependencies = [
|
|
39
|
+
"httpx>=0.27,<1",
|
|
40
|
+
"pydantic>=2.7,<3",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
pandas = ["pandas>=2.0"]
|
|
45
|
+
dev = [
|
|
46
|
+
"build>=1.2",
|
|
47
|
+
"mypy>=1.10",
|
|
48
|
+
"pytest>=8.2",
|
|
49
|
+
"pytest-asyncio>=0.23",
|
|
50
|
+
"respx>=0.21",
|
|
51
|
+
"ruff>=0.5",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.urls]
|
|
55
|
+
Homepage = "https://openserp.org"
|
|
56
|
+
Documentation = "https://openserp.org/docs"
|
|
57
|
+
Repository = "https://github.com/karust/openserp"
|
|
58
|
+
Issues = "https://github.com/karust/openserp/issues"
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel]
|
|
61
|
+
packages = ["src/openserp"]
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
line-length = 100
|
|
65
|
+
target-version = "py310"
|
|
66
|
+
|
|
67
|
+
[tool.ruff.lint]
|
|
68
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
69
|
+
|
|
70
|
+
[tool.mypy]
|
|
71
|
+
python_version = "3.10"
|
|
72
|
+
strict = true
|
|
73
|
+
warn_unreachable = true
|
|
74
|
+
|
|
75
|
+
[tool.pytest.ini_options]
|
|
76
|
+
testpaths = ["tests"]
|
|
77
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from .client import AsyncOpenSERP, OpenSERP
|
|
2
|
+
from .errors import (
|
|
3
|
+
CaptchaError,
|
|
4
|
+
CloudOnlyError,
|
|
5
|
+
OssOnlyError,
|
|
6
|
+
RateLimitError,
|
|
7
|
+
SERPError,
|
|
8
|
+
TimeoutError,
|
|
9
|
+
)
|
|
10
|
+
from .models import (
|
|
11
|
+
Backend,
|
|
12
|
+
CircuitBreakerStatsResponse,
|
|
13
|
+
CloudAccount,
|
|
14
|
+
CreditInfo,
|
|
15
|
+
Engine,
|
|
16
|
+
EnginesCapabilities,
|
|
17
|
+
EnginesStatus,
|
|
18
|
+
HealthStatus,
|
|
19
|
+
ImageEnvelope,
|
|
20
|
+
ImageResult,
|
|
21
|
+
LastResponse,
|
|
22
|
+
MegaEnginesResponse,
|
|
23
|
+
MegaMode,
|
|
24
|
+
MegaSearchEnvelope,
|
|
25
|
+
Pricing,
|
|
26
|
+
ProxyStats,
|
|
27
|
+
ReadinessStatus,
|
|
28
|
+
ResponseFormat,
|
|
29
|
+
SearchEnvelope,
|
|
30
|
+
SearchResult,
|
|
31
|
+
StatsResponse,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
__version__ = "0.1.0"
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"AsyncOpenSERP",
|
|
38
|
+
"Backend",
|
|
39
|
+
"CaptchaError",
|
|
40
|
+
"CircuitBreakerStatsResponse",
|
|
41
|
+
"CloudAccount",
|
|
42
|
+
"CloudOnlyError",
|
|
43
|
+
"CreditInfo",
|
|
44
|
+
"Engine",
|
|
45
|
+
"EnginesCapabilities",
|
|
46
|
+
"EnginesStatus",
|
|
47
|
+
"HealthStatus",
|
|
48
|
+
"ImageEnvelope",
|
|
49
|
+
"ImageResult",
|
|
50
|
+
"LastResponse",
|
|
51
|
+
"MegaEnginesResponse",
|
|
52
|
+
"MegaMode",
|
|
53
|
+
"MegaSearchEnvelope",
|
|
54
|
+
"OpenSERP",
|
|
55
|
+
"OssOnlyError",
|
|
56
|
+
"Pricing",
|
|
57
|
+
"ProxyStats",
|
|
58
|
+
"RateLimitError",
|
|
59
|
+
"ReadinessStatus",
|
|
60
|
+
"ResponseFormat",
|
|
61
|
+
"SERPError",
|
|
62
|
+
"SearchEnvelope",
|
|
63
|
+
"SearchResult",
|
|
64
|
+
"StatsResponse",
|
|
65
|
+
"TimeoutError",
|
|
66
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from urllib.parse import urlparse
|
|
4
|
+
|
|
5
|
+
from .models import Backend
|
|
6
|
+
|
|
7
|
+
OSS_BASE_URL = "http://localhost:7000"
|
|
8
|
+
CLOUD_BASE_URL = "https://api.openserp.org/v1"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_base_url(base_url: str) -> str:
|
|
12
|
+
return base_url.rstrip("/")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def resolve_base_url(api_key: str | None = None, base_url: str | None = None) -> str:
|
|
16
|
+
if base_url:
|
|
17
|
+
return normalize_base_url(base_url)
|
|
18
|
+
if api_key:
|
|
19
|
+
return CLOUD_BASE_URL
|
|
20
|
+
return OSS_BASE_URL
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def infer_backend(
|
|
24
|
+
api_key: str | None = None,
|
|
25
|
+
base_url: str | None = None,
|
|
26
|
+
backend: Backend | None = None,
|
|
27
|
+
) -> Backend:
|
|
28
|
+
if backend:
|
|
29
|
+
return backend
|
|
30
|
+
|
|
31
|
+
if base_url:
|
|
32
|
+
try:
|
|
33
|
+
if urlparse(base_url).hostname == "api.openserp.org":
|
|
34
|
+
return "cloud"
|
|
35
|
+
except ValueError:
|
|
36
|
+
if "api.openserp.org" in base_url:
|
|
37
|
+
return "cloud"
|
|
38
|
+
|
|
39
|
+
if api_key:
|
|
40
|
+
return "cloud"
|
|
41
|
+
|
|
42
|
+
return "oss"
|