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.
@@ -0,0 +1,8 @@
1
+ .mypy_cache/
2
+ .pytest_cache/
3
+ .ruff_cache/
4
+ .venv/
5
+ .venv-*/
6
+ __pycache__/
7
+ dist/
8
+ *.egg-info/
@@ -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
+ ```
@@ -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"