lfx-serpingapi 0.1.0__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.
@@ -0,0 +1,15 @@
1
+ """lfx-serpingapi: Serping API Search bundle.
2
+
3
+ This package is the distribution unit ``lfx-serpingapi``. At runtime
4
+ Langflow's loader discovers ``extension.json`` shipped alongside this
5
+ ``__init__.py`` and registers ``SerpingApiSearchComponent`` under the
6
+ namespaced ID ``ext:serpingapi:SerpingApiSearchComponent@official``.
7
+
8
+ Serping API (https://serpingapi.com) is a Google SERP API; the component
9
+ calls its search endpoint directly with ``httpx`` and needs only a
10
+ user-supplied API key, so the bundle carries no vendor SDK dependency.
11
+ """
12
+
13
+ from lfx_serpingapi.components.serpingapi.serpingapi_search import SerpingApiSearchComponent
14
+
15
+ __all__ = ["SerpingApiSearchComponent"]
@@ -0,0 +1,3 @@
1
+ from .serpingapi_search import SerpingApiSearchComponent
2
+
3
+ __all__ = ["SerpingApiSearchComponent"]
@@ -0,0 +1,176 @@
1
+ import httpx
2
+ from lfx.custom.custom_component.component import Component
3
+ from lfx.field_typing.range_spec import RangeSpec
4
+ from lfx.inputs.inputs import IntInput, MessageTextInput, SecretStrInput
5
+ from lfx.schema.data import Data
6
+ from lfx.schema.dataframe import DataFrame
7
+ from lfx.template.field.base import Output
8
+
9
+ SEARCH_ENDPOINT = "https://api.serpingapi.com/v1/search"
10
+ DEFAULT_RESULTS = 10
11
+ MIN_RESULTS = 1
12
+ MAX_RESULTS = 100
13
+ FIRST_PAGE = 1
14
+
15
+
16
+ def _http_error_message(error: httpx.HTTPStatusError) -> str:
17
+ """Describe a non-2xx Serping API response, keeping the API's own error message.
18
+
19
+ Serping API errors carry ``{"error": {"code": ..., "message": ...}}``.
20
+ """
21
+ response = error.response
22
+ detail = None
23
+ try:
24
+ payload = response.json()
25
+ body_error = payload.get("error") if isinstance(payload, dict) else None
26
+ if isinstance(body_error, dict):
27
+ detail = body_error.get("message") or body_error.get("code")
28
+ elif isinstance(body_error, str):
29
+ detail = body_error
30
+ except ValueError:
31
+ detail = None
32
+ reason = detail or response.reason_phrase or "request failed"
33
+ return f"Serping API error {response.status_code}: {reason}"
34
+
35
+
36
+ class SerpingApiSearchComponent(Component):
37
+ """Component for performing web searches using the Serping API."""
38
+
39
+ display_name = "Serping API Search"
40
+ description = "Search Google organic results with the Serping API."
41
+ documentation = "https://serpingapi.com/docs"
42
+ icon = "search"
43
+
44
+ inputs = [
45
+ MessageTextInput(
46
+ name="input_value",
47
+ display_name="Search Query",
48
+ required=True,
49
+ info="The search query to execute with the Serping API.",
50
+ tool_mode=True,
51
+ ),
52
+ SecretStrInput(
53
+ name="serpingapi_api_key",
54
+ display_name="Serping API Key",
55
+ required=True,
56
+ info="Your Serping API key. Get one at https://serpingapi.com.",
57
+ password=True,
58
+ ),
59
+ MessageTextInput(
60
+ name="gl",
61
+ display_name="Country",
62
+ required=False,
63
+ advanced=True,
64
+ info="Country code for the results, for example 'us', 'de' or 'jp'.",
65
+ ),
66
+ MessageTextInput(
67
+ name="hl",
68
+ display_name="Language",
69
+ required=False,
70
+ advanced=True,
71
+ info="Interface language, for example 'en' or 'es'.",
72
+ ),
73
+ MessageTextInput(
74
+ name="location",
75
+ display_name="Location",
76
+ required=False,
77
+ advanced=True,
78
+ info="Locality for the search, for example 'Seattle, Washington, United States'.",
79
+ ),
80
+ IntInput(
81
+ name="max_results",
82
+ display_name="Max Results",
83
+ value=DEFAULT_RESULTS,
84
+ required=False,
85
+ advanced=True,
86
+ range_spec=RangeSpec(min=MIN_RESULTS, max=MAX_RESULTS, step=1, step_type="int"),
87
+ info="Maximum number of organic results to return (1-100).",
88
+ ),
89
+ IntInput(
90
+ name="page",
91
+ display_name="Page",
92
+ value=FIRST_PAGE,
93
+ required=False,
94
+ advanced=True,
95
+ range_spec=RangeSpec(min=FIRST_PAGE, max=100, step=1, step_type="int"),
96
+ info="Result page to fetch, starting at 1.",
97
+ ),
98
+ ]
99
+
100
+ outputs = [
101
+ # The method name is also the tool name in tool mode. Keep it specific:
102
+ # DuckDuckGo, Tavily, Wikipedia and other search components already expose
103
+ # ``fetch_content_dataframe``, and an Agent cannot hold two tools with the
104
+ # same name.
105
+ Output(display_name="Table", name="dataframe", method="serpingapi_search"),
106
+ ]
107
+
108
+ def _request_body(self) -> dict:
109
+ """Build the JSON body for the Serping API search endpoint."""
110
+ requested = DEFAULT_RESULTS if self.max_results is None else int(self.max_results)
111
+ num = max(MIN_RESULTS, min(requested, MAX_RESULTS))
112
+ body: dict = {"q": self.input_value or "", "num": num}
113
+ for field in ("gl", "hl", "location"):
114
+ value = getattr(self, field, None)
115
+ if isinstance(value, str) and value.strip():
116
+ body[field] = value.strip()
117
+ page = FIRST_PAGE if self.page is None else int(self.page)
118
+ if page > FIRST_PAGE:
119
+ body["page"] = page
120
+ return body
121
+
122
+ def _search(self) -> dict:
123
+ """Call the Serping API search endpoint and return the decoded JSON payload."""
124
+ if not self.serpingapi_api_key:
125
+ msg = "Serping API key is required. Set the Serping API Key input."
126
+ raise ValueError(msg)
127
+
128
+ headers = {
129
+ "X-API-Key": self.serpingapi_api_key,
130
+ "Accept": "application/json",
131
+ "Content-Type": "application/json",
132
+ # Identify the integration to the API; keeps traffic attributable.
133
+ "User-Agent": "langflow-serpingapi-bundle",
134
+ }
135
+ response = httpx.post(SEARCH_ENDPOINT, json=self._request_body(), headers=headers, timeout=30)
136
+ response.raise_for_status()
137
+ return response.json()
138
+
139
+ def _error_result(self, message: str) -> list[Data]:
140
+ error_data = [Data(text=message, data={"error": message})]
141
+ self.status = error_data
142
+ return error_data
143
+
144
+ @staticmethod
145
+ def _result_to_data(result: dict) -> Data:
146
+ snippet = result.get("snippet", "")
147
+ return Data(
148
+ text=snippet,
149
+ data={
150
+ "title": result.get("title", ""),
151
+ "link": result.get("link", ""),
152
+ "snippet": snippet,
153
+ "position": result.get("position"),
154
+ },
155
+ )
156
+
157
+ def fetch_content(self) -> list[Data]:
158
+ """Execute the search and return the organic results as Data objects."""
159
+ try:
160
+ payload = self._search()
161
+ except httpx.HTTPStatusError as e:
162
+ return self._error_result(_http_error_message(e))
163
+ except (httpx.HTTPError, ValueError) as e:
164
+ return self._error_result(str(e))
165
+
166
+ if not isinstance(payload, dict):
167
+ return self._error_result("Serping API returned an unexpected response (expected a JSON object).")
168
+
169
+ results = payload.get("organic") or []
170
+ data_results = [self._result_to_data(result) for result in results if isinstance(result, dict)]
171
+ self.status = data_results
172
+ return data_results
173
+
174
+ def serpingapi_search(self) -> DataFrame:
175
+ """Run the search and return the organic results as a DataFrame."""
176
+ return DataFrame(self.fetch_content())
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://schemas.langflow.org/extension/v1.json",
3
+ "id": "lfx-serpingapi",
4
+ "version": "0.1.0",
5
+ "name": "Serping API",
6
+ "description": "Serping API Google SERP web-search component as a standalone Langflow Extension Bundle.",
7
+ "lfx": {
8
+ "compat": ["1"]
9
+ },
10
+ "bundles": [
11
+ {
12
+ "name": "serpingapi",
13
+ "path": "components/serpingapi"
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: lfx-serpingapi
3
+ Version: 0.1.0
4
+ Summary: Serping API Google SERP web-search component as a standalone Langflow Extension Bundle.
5
+ Project-URL: Homepage, https://github.com/langflow-ai/langflow
6
+ Project-URL: Documentation, https://docs.langflow.org/extensions
7
+ Project-URL: Repository, https://github.com/langflow-ai/langflow
8
+ Author-email: Langflow <contact@langflow.org>
9
+ License: MIT
10
+ Keywords: bundle,extension,langflow,lfx,search,serp,serpingapi
11
+ Requires-Python: <3.15,>=3.10
12
+ Requires-Dist: httpx<1.0.0,>=0.24.0
13
+ Requires-Dist: lfx<2.0.0,>=1.13.0.dev0
14
+ Description-Content-Type: text/markdown
15
+
16
+ # lfx-serpingapi
17
+
18
+ Serping API Google SERP web-search component as a standalone Langflow Extension Bundle.
19
+
20
+ The bundle ships a single component, `SerpingApiSearchComponent`, which runs
21
+ a web search through the [Serping API](https://serpingapi.com) and returns
22
+ the organic Google results as a table. It calls the Serping API search
23
+ endpoint directly with `httpx` and needs only a user-supplied API key, so it
24
+ carries no vendor SDK dependency. See the
25
+ [Serping API docs](https://serpingapi.com/docs) for the API details.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install lfx-serpingapi
31
+ ```
32
+
33
+ The bundle is registered automatically via the `langflow.extensions`
34
+ entry-point. After install, restart your Langflow server; the
35
+ `SerpingApiSearchComponent` will appear in the palette's **Bundles** section
36
+ under **Serping API**.
37
+
38
+ ## Configure
39
+
40
+ Set the **Serping API Key** input to your own key from
41
+ [serpingapi.com](https://serpingapi.com). The component is optional and does
42
+ nothing until a key is supplied, so it changes nothing for anyone who does
43
+ not use it. Country (`gl`), language (`hl`), location, max results (1-100)
44
+ and page are optional advanced inputs.
45
+
46
+ In tool mode, the component exposes a single tool named `serpingapi_search`.
47
+
48
+ ## Develop
49
+
50
+ ```bash
51
+ cd src/bundles/serpingapi
52
+ pip install -e .
53
+ lfx extension validate src/lfx_serpingapi
54
+ ```
55
+
56
+ ## Manifest
57
+
58
+ The extension manifest is shipped at `src/lfx_serpingapi/extension.json` and
59
+ points at the bundle at `components/serpingapi`. The component registers under
60
+ the canonical namespaced ID `ext:serpingapi:SerpingApiSearchComponent@official`.
@@ -0,0 +1,8 @@
1
+ lfx_serpingapi/__init__.py,sha256=V21xnObdW36K33lDOBtxd76w8_VSu21Qh3Q5VVb-FJ8,677
2
+ lfx_serpingapi/extension.json,sha256=xVpiUGUFfItir6z60HPj2InWGuGaY_EebszTTziu9yY,381
3
+ lfx_serpingapi/components/serpingapi/__init__.py,sha256=W0iZQhQxVWg2P_OOGpMw-zdSkFMI6hW0paFhxWJJ4KA,98
4
+ lfx_serpingapi/components/serpingapi/serpingapi_search.py,sha256=QXobb-9CkGsf-mAGPdl0COLgxjEJxpO6uHk9pXpcOK4,6671
5
+ lfx_serpingapi-0.1.0.dist-info/METADATA,sha256=xGXCdAWMZkYmgZy_BoRGENSN99U60izfjN4Vc_tmuuU,2193
6
+ lfx_serpingapi-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
7
+ lfx_serpingapi-0.1.0.dist-info/entry_points.txt,sha256=GhaFOfWCRihPgSutE5n5E7kDmzlxga20O0_o86vMlLA,54
8
+ lfx_serpingapi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [langflow.extensions]
2
+ lfx-serpingapi = lfx_serpingapi