Skip to content

Commit c124b21

Browse files
committed
Merge remote-tracking branch 'origin/master' into feat/docs-enhancements
2 parents 897d928 + 9f79696 commit c124b21

15 files changed

Lines changed: 579 additions & 44 deletions

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
18+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1919

2020
steps:
2121
- name: Checkout repository
@@ -36,7 +36,7 @@ jobs:
3636
API_KEY: ${{ secrets.API_KEY }}
3737

3838
- name: Run example tests
39-
if: matrix.python-version == '3.13'
39+
if: matrix.python-version == '3.14'
4040
run: pytest -k "example"
4141
env:
4242
API_KEY: ${{ secrets.API_KEY }}

.github/workflows/release.yml

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ name: Release
33
on:
44
push:
55
tags: ['v**']
6+
workflow_dispatch:
7+
inputs:
8+
allow_example_test_failures:
9+
description: 'Publish even if the live example tests fail (use only when a SerpApi engine is down)'
10+
type: boolean
11+
default: false
612

713
concurrency:
814
group: ${{ github.workflow }}-${{ github.ref }}
@@ -14,10 +20,16 @@ jobs:
1420
runs-on: ubuntu-latest
1521
strategy:
1622
matrix:
17-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
23+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1824
steps:
1925
- uses: actions/checkout@v6
2026

27+
- name: Ensure ref is a tag
28+
if: github.ref_type != 'tag'
29+
run: |
30+
echo "Release must run against a tag, got '${{ github.ref }}'" >&2
31+
exit 1
32+
2133
- uses: actions/setup-python@v6
2234
with:
2335
python-version: ${{ matrix.python-version }}
@@ -31,7 +43,8 @@ jobs:
3143
API_KEY: ${{ secrets.API_KEY }}
3244

3345
- name: Run example tests
34-
if: matrix.python-version == '3.13'
46+
if: matrix.python-version == '3.14'
47+
continue-on-error: ${{ inputs.allow_example_test_failures == true }}
3548
run: pytest -k "example"
3649
env:
3750
API_KEY: ${{ secrets.API_KEY }}
@@ -62,19 +75,18 @@ jobs:
6275
name: Publish release
6376
needs: [build]
6477
runs-on: ubuntu-latest
78+
environment:
79+
name: pypi
80+
url: https://pypi.org/p/serpapi
6581
permissions:
6682
contents: write
67-
packages: write
83+
id-token: write
6884
steps:
6985
- uses: actions/download-artifact@v8
7086
with:
7187
name: dist
7288
path: dist/
7389

74-
- uses: actions/setup-python@v6
75-
with:
76-
python-version: "3.13"
77-
7890
- name: Create GitHub Release
7991
env:
8092
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -83,14 +95,8 @@ jobs:
8395
gh release create ${{ github.ref_name }} dist/* --generate-notes \
8496
|| gh release upload ${{ github.ref_name }} dist/* --clobber
8597
86-
- name: Install twine
87-
run: pip install --upgrade pip twine
88-
8998
- name: Publish to PyPI
90-
env:
91-
TWINE_USERNAME: __token__
92-
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
93-
run: twine upload dist/*
99+
uses: pypa/gh-action-pypi-publish@release/v1
94100

95101
smoke-test:
96102
name: Smoke test published package

HISTORY.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Release History
22
===============
33

4+
1.1.0 (2026-08-14)
5+
------------------
6+
7+
- Add PyPI trusted publishing. PYPI Key is no longer required for publishing the package.
8+
- Add image upload support (`/image` API) for Google Lens
9+
- Release workflow: Add an option to manually trigger the release workflow and skip live tests
10+
- Add more test cases: Locations API, pagination, etc.
11+
412
1.0.1 (2026-03-18)
513
------------------
614

README.md

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,22 @@ Documentation is [available on Read the Docs](https://serpapi-python.readthedocs
7979

8080
Change history is [available on GitHub](https://github.com/serpapi/serpapi-python/blob/master/HISTORY.md).
8181

82+
### Markdown output for AI agents
83+
84+
For AI agents and LLM workflows, use `output="md"`. It returns search results with clean headings, links, and tables while using roughly half the tokens of JSON on average.
85+
86+
```python
87+
markdown = client.search({
88+
"engine": "google",
89+
"q": "coffee",
90+
"output": "md",
91+
})
92+
```
93+
94+
Markdown works across SerpApi APIs and is returned as a plain Python string. See [Markdown Output for AI Agents](https://serpapi.com/markdown-output.md) for details.
95+
96+
Raw HTML is also available with `output="html"` and is returned as a plain Python string.
97+
8298
## Basic Examples in Python
8399

84100
### Search Bing
@@ -277,6 +293,38 @@ results = client.search({
277293
```
278294
- API Documentation: [serpapi.com/google-reverse-image](https://serpapi.com/google-reverse-image)
279295

296+
### Search Google Lens by image URL or upload
297+
298+
Google Lens accepts either a publicly accessible image URL or an uploaded
299+
image. To search by URL, pass the URL directly:
300+
301+
```python
302+
import os
303+
import serpapi
304+
305+
client = serpapi.Client(api_key=os.getenv("SERPAPI_KEY"))
306+
results = client.search({
307+
"engine": "google_lens",
308+
"url": "https://i.imgur.com/HBrB8p0.png",
309+
})
310+
```
311+
312+
To search a local image, upload it first and pass its temporary `image_id` to
313+
Google Lens:
314+
315+
```python
316+
upload = client.upload_image("/path/to/image.png")
317+
results = client.search({
318+
"engine": "google_lens",
319+
"image_id": upload["image_id"],
320+
})
321+
```
322+
323+
Uploaded images can be JPG/JPEG, PNG, or WebP files up to 500 KB. The returned
324+
`image_id` expires after 10 minutes.
325+
326+
- API Documentation: [Google Lens image uploads](https://serpapi.com/google-lens-upload-an-image), [Image API](https://serpapi.com/image-api)
327+
280328
### Search Google Events
281329
```python
282330
import os
@@ -380,4 +428,4 @@ Bug reports and pull requests are welcome on GitHub. Once dependencies are insta
380428
```
381429
This triggers the [release workflow](.github/workflows/release.yml), which tests, builds, and publishes to PyPI, then smoke-tests the published package.
382430

383-
> **Required secrets:** `PYPI_API_TOKEN` (PyPI upload token) and `API_KEY` (used in smoke-test live search).
431+
> **Required secret:** `API_KEY` (used in smoke-test live search).

docs/index.rst

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
.. serpapi-python documentation master file, created by
2+
sphinx-quickstart on Sun Apr 3 21:09:40 2022.
3+
You can adapt this file completely to your liking, but it should at least
4+
contain the root `toctree` directive.
5+
6+
**serpapi-python**
7+
==================
8+
9+
an official Python client library for `SerpApi <https://serpapi.com>`_.
10+
11+
--------------
12+
13+
Installation
14+
------------
15+
16+
To install ``serpapi-python``, simply use `pip`::
17+
18+
$ pip install serpapi
19+
20+
21+
Please note that Python 3.6+ is required.
22+
23+
24+
Usage
25+
-----
26+
27+
Usage of this module is fairly straight-forward. In general, this module attempts to be as close to the actual API as possible, while still being Pythonic.
28+
29+
For example, the API endpoint ``https://serpapi.com/search.json`` is represented by the method ``serpapi.search()``.
30+
31+
.. code-block:: python
32+
33+
>>> import serpapi
34+
>>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us")
35+
>>> s["organic_results"][0]["link"]
36+
'https://en.wikipedia.org/wiki/Coffee'
37+
38+
Any parameters that you pass to ``search()`` will be passed to the API. This includes the ``api_key`` parameter, which is required for all requests.
39+
40+
.. _using-api-client-directly:
41+
42+
Using the API Client directly
43+
^^^^^^^^^
44+
45+
To make this less repetitive, and gain the benefit of connection pooling, let's start using the API Client directly::
46+
47+
>>> client = serpapi.Client(api_key="secret_api_key")
48+
>>> s = client.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us")
49+
50+
The ``api_key`` parameter is now automatically passed to all requests made by the client.
51+
52+
53+
Concise Tutorial
54+
----------------
55+
56+
Let's start by searching for ``Coffee`` on Google::
57+
58+
>>> import serpapi
59+
>>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us")
60+
61+
The ``s`` variable now contains a :class:`SerpResults <serpapi.SerpResults>` object, which acts just like a standard dictionary, with some convenient functions added on top.
62+
63+
Let's print the first result::
64+
65+
>>> print(s["organic_results"][0]["link"])
66+
https://en.wikipedia.org/wiki/Coffee
67+
68+
Let's print the title of the first result, but in a more Pythonic way::
69+
70+
>>> print(s["organic_results"][0].get("title"))
71+
Coffee - Wikipedia
72+
73+
The `SerpApi.com API Documentation <https://serpapi.com/search-api>`_ contains a list of all the possible parameters that can be passed to the API.
74+
75+
76+
API Reference
77+
-------------
78+
79+
.. _api-reference:
80+
81+
This part of the documentation covers all the interfaces of :class:`serpapi` Python module.
82+
83+
.. module:: serpapi
84+
:platform: Unix, Windows
85+
:synopsis: SerpApi Python Library
86+
87+
.. autofunction:: serpapi.search
88+
.. autofunction:: serpapi.search_archive
89+
.. autofunction:: serpapi.upload_image
90+
.. autofunction:: serpapi.locations
91+
.. autofunction:: serpapi.account
92+
93+
94+
95+
Results from SerpApi.com
96+
------------------------
97+
98+
When a successful search has been executed, the method returns
99+
a :class:`SerpResults <serpapi.SerpResults>` object, which acts just like a standard dictionary,
100+
with some convenient functions added on top.
101+
102+
103+
.. code-block:: python
104+
105+
>>> s = serpapi.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us")
106+
>>> type(s)
107+
<class 'serpapi.models.SerpResults'>
108+
109+
>>> s["organic_results"][0]["link"]
110+
'https://en.wikipedia.org/wiki/Coffee'
111+
112+
>>> s["search_metadata"]
113+
{'id': '64c148d35119a60ab1e00cc9', 'status': 'Success', 'json_endpoint': 'https://serpapi.com/searches/a15e1b92727f292c/64c148d35119a60ab1e00cc9.json', 'created_at': '2023-07-26 16:24:51 UTC', 'processed_at': '2023-07-26 16:24:51 UTC', 'google_url': 'https://www.google.com/search?q=Coffee&oq=Coffee&uule=w+CAIQICIdQXVzdGluLFRYLFRleGFzLFVuaXRlZCBTdGF0ZXM&hl=en&gl=us&sourceid=chrome&ie=UTF-8', 'raw_html_file': 'https://serpapi.com/searches/a15e1b92727f292c/64c148d35119a60ab1e00cc9.html', 'total_time_taken': 1.55}
114+
115+
Optionally, if you want exactly a dictionary of the entire response, you can use the ``as_dict()`` method::
116+
117+
>>> type(s.as_dict())
118+
<class 'dict'>
119+
120+
You can get the next page of results::
121+
122+
>>> type(s.next_page())
123+
<class 'serpapi.models.SerpResults'>
124+
125+
To iterate over all pages of results, it's recommended to :ref:`use the API Client directly <using-api-client-directly>`::
126+
127+
>>> client = serpapi.Client(api_key="secret_api_key")
128+
>>> search = client.search(q="Coffee", engine="google", location="Austin, Texas", hl="en", gl="us")
129+
>>> for page in search.yield_pages():
130+
... print(page["search_metadata"]["page_number"])
131+
1
132+
2
133+
3
134+
4
135+
5
136+
6
137+
7
138+
8
139+
9
140+
10
141+
142+
143+
Here's documentation of the class itself and its methods:
144+
145+
.. autoclass:: serpapi.SerpResults
146+
147+
.. automethod:: SerpResults.next_page
148+
.. automethod:: SerpResults.yield_pages
149+
.. autoproperty:: SerpResults.next_page_url
150+
151+
152+
API Client
153+
----------
154+
155+
The primary interface to `serpapi-python` is through the :class:`serpapi.Client` class.
156+
The primary benefit of using this class is to benefit from Requests' HTTP Connection Pooling.
157+
This class also alleviates the need to pass an ``api_key``` along with every search made to the platform.
158+
159+
.. autoclass:: serpapi.Client
160+
161+
.. automethod:: Client.search
162+
.. automethod:: Client.search_archive
163+
.. automethod:: Client.upload_image
164+
.. automethod:: Client.account
165+
.. automethod:: Client.locations
166+
167+
168+
169+
Exceptions
170+
----------
171+
172+
.. autoexception:: serpapi.SerpApiError
173+
:members:
174+
175+
.. autoexception:: serpapi.SearchIDNotProvided
176+
:members:
177+
178+
.. autoexception:: serpapi.HTTPError
179+
:members:
180+
181+
.. autoexception:: serpapi.HTTPConnectionError
182+
:members:
183+
184+
185+
186+
187+
188+
Indices and tables
189+
==================
190+
191+
* :ref:`genindex`
192+
* :ref:`modindex`
193+
* :ref:`search`

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ classifiers = [
2828
"Programming Language :: Python :: 3.11",
2929
"Programming Language :: Python :: 3.12",
3030
"Programming Language :: Python :: 3.13",
31+
"Programming Language :: Python :: 3.14",
3132
"Programming Language :: Python :: Implementation :: CPython",
3233
"Natural Language :: English",
3334
"Topic :: Utilities",

serpapi/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.2"
1+
__version__ = "1.1.0"

0 commit comments

Comments
 (0)